2 * Thrasher Bird - XMPP transport via libpurple
3 * Copyright (C) 2008 Barracuda Networks, Inc.
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with Thrasher Bird; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
27 #include "thpresence.h"
28 #include "thconversations.h"
37 /* Internal prototypes */
38 int func_call(gpointer func
);
39 void destroy_record_key (gpointer key
);
40 void validate_callback (SV
*cb
);
41 void thrasher_wrapper_destructor (void);
42 SV
*clean_newSVpv(const char *inp
);
43 void thrasher_remove_account(const char *jid
);
45 perl_callbacks callback
;
46 perl_ft_callbacks ft_callback
;
49 char *trigger_timeout_func_name
= "THPPW::thrasher_wrapper_trigger_timeout_func";
50 char *trigger_input_func_name
= "THPPW::thrasher_wrapper_trigger_input_func";
53 GHashTable
*accts
= NULL
;
54 GHashTable
*callback_funcs
= NULL
;
55 GHashTable
*perl_key_lookup
= NULL
;
57 void thrasher_purple_debug (int tf
)
59 /* Flip on libpurple debugging info */
60 purple_debug_set_enabled(tf
);
64 void thrasher_perl_init()
66 accts
= g_hash_table_new(g_str_hash
, g_str_equal
);
70 * @brief create a PurpleAccount and start connecting it.
71 * @param hash_args of arguments for thrasher_login().
72 * @return 0 for success. 1 if thrasher_login() fails. 2 if the PurpleAccount already exists.
74 int thrasher_action_login (GHashTable
*hash_args
)
79 purple_debug_info("thrasher",
81 GList
*keys
= g_hash_table_get_keys(hash_args
);
82 GList
*values
= g_hash_table_get_values(hash_args
);
85 purple_debug_info("thrasher",
88 (char *)values
->data
);
90 values
= values
->next
;
97 // some parameters we always have
98 char *jid
= (char *)g_strdup(g_hash_table_lookup(hash_args
, "jid"));
99 char *username
= (char *)g_strdup(g_hash_table_lookup(hash_args
, "username"));
100 char *password
= (char *)g_strdup(g_hash_table_lookup(hash_args
, "password"));
101 char *proto
= (char *)g_strdup(g_hash_table_lookup(hash_args
, "proto"));
103 g_return_val_if_fail(jid
, 0);
104 g_return_val_if_fail(username
, 0);
105 g_return_val_if_fail(password
, 0);
106 g_return_val_if_fail(proto
, 0);
108 g_hash_table_remove(hash_args
, "jid");
109 g_hash_table_remove(hash_args
, "username");
110 g_hash_table_remove(hash_args
, "password");
111 g_hash_table_remove(hash_args
, "proto");
113 /* Verify user is not already logged in */
114 if (g_hash_table_lookup(accts
, jid
) == NULL
)
116 PurpleAccount
*account
= thrasher_login(proto
, username
, password
,
119 // In the event of an error, connection_error has already been
121 if (account
== NULL
) {
125 char *new_jid
= g_strdup(jid
);
127 /* Add user to hash */
130 new_jid
, /* Should eventually be the protocol + username */
139 // At least indicate something bad has happened.
140 purple_debug_info("thrasher",
141 "login request for [%s] fail because they are apparently already logged in\n",
150 g_hash_table_destroy(hash_args
);
158 * @brief add buddy (@p buddy_name) for given @p jid buddy list
159 * @param jid releational key to bound to account
160 * @param buddy_name name of buddy
161 * @return 1 if there is a problem, 0 otherwise (C)
163 int thrasher_action_buddy_add (char *jid
, char *buddy_name
)
165 PurpleAccount
*account
= NULL
;
166 PurpleBuddy
*buddy
= NULL
;
167 PurpleGroup
*group
= NULL
;
169 purple_debug_info("thrasher", "Buddy_add request\n");
171 account
= g_hash_table_lookup(accts
, jid
);
172 g_return_val_if_fail(account
!= NULL
, 1);
174 buddy
= purple_find_buddy(account
, buddy_name
);
176 // FIXME: Figure out if we need this buddy list stuff
177 // at all. We don't actually care, as long as we get
178 // the messages and presence to send out.
180 /* We're done if buddy already exists */
183 char *default_group
= "General";
185 /* Check to see if default group exists */
186 group
= purple_find_group(default_group
);
188 /* If not, create default group */
190 group
= purple_group_new(default_group
);
192 purple_blist_add_group(group
, NULL
);
194 /* Add buddy to default group (NULL should be alias...) */
195 buddy
= purple_buddy_new(account
, buddy_name
, NULL
);
196 purple_blist_add_buddy(buddy
, NULL
, group
, NULL
);
197 purple_account_add_buddy(account
, buddy
);
199 purple_debug_info("thrasher",
200 "finished adding %s to %s's buddy list\n",
205 purple_debug_info("thrasher",
206 "%s already on %s's buddy list\n",
210 /* Buddy already exists */
215 * @brief remove buddy (@p buddy_name) from given @p jid buddy list
216 * @param jid releational key to bound to account
217 * @param buddy_name name of buddy
218 * @return 1 if there is a problem, 0 otherwise (C)
220 int thrasher_action_buddy_remove (char *jid
, char *buddy_name
)
222 PurpleGroup
*group
= NULL
;
223 PurpleBuddy
*buddy
= NULL
;
224 PurpleAccount
*account
= NULL
;
226 purple_debug_info("thrasher", "Buddy remove\n");
228 account
= g_hash_table_lookup(accts
, jid
);
229 g_return_val_if_fail(account
!= NULL
, 1);
231 // Don't try and remove a buddy if the account
233 if (purple_account_is_connected(account
)) {
235 purple_debug_info("thrasher", "find_buddy...\n");
236 buddy
= purple_find_buddy(account
, buddy_name
);
238 /* Only remove a buddy if they exist */
241 purple_debug_info("thrasher", "get_group...\n");
242 /* Give up us the group */
243 group
= purple_buddy_get_group(buddy
);
245 purple_debug_info("thrasher", "account_remove_buddy...\n");
246 /* Remove server-side buddy entry */
247 purple_account_remove_buddy(account
, buddy
, group
);
249 purple_debug_info("thrasher", "blist_remove_buddy...\n");
250 /* Also need to remove buddy entry from memory */
251 purple_blist_remove_buddy(buddy
);
257 /* Account isn't connected or buddy is not found */
261 void thrasher_action_buddy_authorize(char *jid
, char *legacy_username
)
263 thrasher_action_buddy_add(jid
, legacy_username
);
266 void thrasher_action_buddy_deauthorize(char *jid
, char *legacy_username
) {
267 thrasher_action_buddy_remove(jid
, legacy_username
);
271 * @brief update presence status for given @p jid
272 * @param jid releational key to bound to account
273 * @param status integer representation of PurpleStatusPrimitive
274 * @param message a status message (may be NULL)
275 * @return 1 if there is a problem, 0 otherwise (C)
277 int thrasher_action_presence (char *jid
, int status
, char *message
)
279 PurpleAccount
*account
;
281 g_return_val_if_fail(jid
!= NULL
, 0);
283 account
= g_hash_table_lookup(accts
, jid
);
288 thrasher_set_presence(account
, status
, message
);
295 * @brief Send @p message from the user given by @p jid to the user @p recipient
299 * @return 1 on success 0 on failure
301 int thrasher_action_outgoing_msg (char *jid
, char *recipient
, char *message
)
304 PurpleAccount
*account
= NULL
;
306 g_return_val_if_fail(jid
!= NULL
, 0);
307 g_return_val_if_fail(recipient
!= NULL
, 0);
308 g_return_val_if_fail(message
!= NULL
, 0);
310 purple_debug_info("thrasher", "Send message request\n");
311 /* Pull in user if they exist */
312 account
= g_hash_table_lookup(accts
, jid
);
314 /* User does not exist (may not be logged in) */
315 if (account
== NULL
) {
319 /* Try to send message (message might not be sent) */
320 if (thrasher_send_msg(account
,recipient
,message
) == TRUE
)
328 * @brief Send @p chatstate from the user given by @p jid to the @p recipient
334 thrasher_action_outgoing_chatstate(char *jid
,
336 PurpleTypingState chatstate
) {
337 g_return_if_fail(jid
!= NULL
);
338 g_return_if_fail(recipient
!= NULL
);
340 PurpleAccount
*pa
= NULL
;
341 pa
= g_hash_table_lookup(accts
, jid
);
343 /* User does not exist (may not be logged in) */
347 /* Try to send chatstate */
348 thrasher_outgoing_chatstate(pa
, recipient
, chatstate
);
353 * @brief Log the user out with the given jid
354 * @param jid releational key to bind to account
355 * @return 1 if logout was successful, 0 if it was not
357 * NOTE: As we're going to be running a process for each protocol,
358 * there's no need to worry about the protocol used.
360 int thrasher_action_logout (char *jid
)
363 PurpleAccount
*account
;
365 g_return_val_if_fail(jid
!= NULL
, 0);
367 purple_debug_info("thrasher", "Logout requested for %s\n", jid
);
369 /* Verify user is logged in */
370 account
=g_hash_table_lookup(accts
, jid
);
373 thrasher_logout(account
);
374 thrasher_remove_account(jid
);
382 void _thrasher_action_debug_logged_in_accts_printer(gpointer key
, gpointer value
, gpointer user_data
);
385 * @brief Dump active account name lists to STDERR from libpurple and thperl.
388 thrasher_action_debug_logged_in() {
389 fprintf(stderr
, "purple_accounts_get_all_active:\n");
390 GList
* list
= purple_accounts_get_all_active();
394 PurpleAccount
*pa
= (PurpleAccount
*)l
->data
;
395 fprintf(stderr
, "\t%s = %p\n", purple_account_get_username(pa
), pa
);
402 fprintf(stderr
, "thperl accts:\n");
403 g_hash_table_foreach(accts
,
404 _thrasher_action_debug_logged_in_accts_printer
,
408 fprintf(stderr
, "No thperl accts?!!\n");
413 _thrasher_action_debug_logged_in_accts_printer(gpointer key
,
415 gpointer user_data
) {
416 const char *jid
= (char*)key
;
417 PurpleAccount
*pa
= (PurpleAccount
*)value
;
418 fprintf(stderr
, "\t%s = %s = %p\n",
419 jid
, purple_account_get_username(pa
), pa
);
424 * @brief Destruction function for our internal function hash
425 * @param key data element
427 void destroy_record_key (gpointer key
)
434 * @brief Bizarro passthrough to allow perl calling of dynamically-set C events
435 * @param key a guint value corresponding to the pointer of a callback.
436 * @return boolean as int (0 || 1) on success. Returns 0 on failure as well.
438 int thrasher_wrapper_trigger_timeout_func(long key
)
440 /* @exception key cannot be NULL */
441 g_return_val_if_fail(key
!= TNULL
,TFALSE
);
443 char *real_key
= LONG_TO_PTR(key
);
445 return func_call(real_key
);
449 * @brief Bizarro passthrough (two) to allow perl calling of dynamically-set C events
450 * @param file descriptor
451 * @param condition trigger was called on
452 * @param key a long value corresponding to the pointer of a callback.
453 * @return boolean as int (0 || 1) on success. Returns 0 on failure as well.
455 int thrasher_wrapper_trigger_input_func(long fd
, SV
*cond
, long key
)
457 /* @exception fd cannot be zero */
458 g_return_val_if_fail(fd
!= 0, 0);
459 /* @exception cond cannot be zero */
460 g_return_val_if_fail(cond
!= 0, 0);
461 /* @exception key cannot be zero */
462 g_return_val_if_fail(key
!= 0, 0);
464 // GIOCondition real_cond = cond;
465 char *real_key
= LONG_TO_PTR(key
);
467 /* FIXME: We need to pass cond and key to this call... */
468 return func_call(real_key
);
472 * @brief Simple verifcation for callbacks
473 * @param subref to callback
476 void validate_callback (SV
*cb
)
478 if (!SvROK(cb
) || SvTYPE(SvRV(cb
)) != SVt_PVCV
)
480 croak("Not a callback!");
487 * @brief Initialize our wrapper
488 * @param subref (SV *) to call for timeout_add
489 * @param subref (SV *) to call for input_add
490 * @param subref (SV *) to call for source_remove
491 * @param subref (SV *) to call on incoming messages
492 * @param subref (SV *) to call on incoming presence information
493 * @param subref (SV *) to call on subscription requests
494 * @param subref (SV *) to call on subscription requests from legacy users
495 * @param subref (SV *) to call on connection errors
496 * @param subref (SV *) to call when connections have succeeded
498 void thrasher_wrapper_init (SV
*timeout_add_cb
,
500 SV
*source_remove_cb
,
503 SV
*subscription_add_cb
,
504 SV
*legacy_user_add_user_cb
,
505 SV
*connection_error_cb
,
507 SV
*incoming_chatstate_cb
)
510 g_return_if_fail(timeout_add_cb
);
511 g_return_if_fail(input_add_cb
);
512 g_return_if_fail(source_remove_cb
);
513 g_return_if_fail(incoming_msg_cb
);
514 g_return_if_fail(presence_in_cb
);
515 g_return_if_fail(subscription_add_cb
);
516 g_return_if_fail(legacy_user_add_user_cb
);
517 g_return_if_fail(connection_error_cb
);
518 g_return_if_fail(connection_cb
);
519 g_return_if_fail(incoming_chatstate_cb
);
521 /* Set the bindings for our function table */
522 callback_funcs
= g_hash_table_new_full(g_direct_hash
,
527 /* Create the lookup table so we know what to do when Perl triggers a remove */
528 perl_key_lookup
= g_hash_table_new(g_int_hash
, g_int_equal
);
530 /* Validate callback, this burns up if it fails so no
531 * need to check for errors!
533 validate_callback(timeout_add_cb
);
534 validate_callback(input_add_cb
);
535 validate_callback(source_remove_cb
);
536 validate_callback(incoming_msg_cb
);
537 validate_callback(presence_in_cb
);
538 validate_callback(subscription_add_cb
);
539 validate_callback(legacy_user_add_user_cb
);
540 validate_callback(connection_error_cb
);
541 validate_callback(connection_cb
);
542 validate_callback(incoming_chatstate_cb
);
552 callback
.timeout_add
= newSVsv(timeout_add_cb
);
553 callback
.input_add
= newSVsv(input_add_cb
);
554 callback
.source_remove
= newSVsv(source_remove_cb
);
555 callback
.incoming_msg
= newSVsv(incoming_msg_cb
);
556 callback
.presence_in
= newSVsv(presence_in_cb
);
557 callback
.subscription_add
= newSVsv(subscription_add_cb
);
558 callback
.legacy_user_add_user
= newSVsv(legacy_user_add_user_cb
);
559 callback
.connection_error
= newSVsv(connection_error_cb
);
560 callback
.connection
= newSVsv(connection_cb
);
561 callback
.incoming_chatstate
= newSVsv(incoming_chatstate_cb
);
571 * @brief Initialize our file transfer wrapper
572 * @param subref (SV *) to call for recv_accept
573 * @param subref (SV *) to call for recv_start
574 * @param subref (SV *) to call for recv_cancel
575 * @param subref (SV *) to call for recv_complete
576 * @param subref (SV *) to call for send_accept
577 * @param subref (SV *) to call for send_start
578 * @param subref (SV *) to call for send_cancel
579 * @param subref (SV *) to call for send_complete
580 * @param subref (SV *) to call for ui_read. Must return (buffer, size).
581 * @param subref (SV *) to call for ui_write
582 * @param subref (SV *) to call for ui_data_not_sent
584 void thrasher_wrapper_ft_init(SV
*ft_recv_request_cb
,
585 SV
*ft_recv_accept_cb
,
586 SV
*ft_recv_start_cb
,
587 SV
*ft_recv_cancel_cb
,
588 SV
*ft_recv_complete_cb
,
589 SV
*ft_send_accept_cb
,
590 SV
*ft_send_start_cb
,
591 SV
*ft_send_cancel_cb
,
592 SV
*ft_send_complete_cb
,
595 SV
*ft_data_not_sent_cb
)
597 g_return_if_fail(ft_recv_request_cb
);
598 g_return_if_fail(ft_recv_accept_cb
);
599 g_return_if_fail(ft_recv_start_cb
);
600 g_return_if_fail(ft_recv_cancel_cb
);
601 g_return_if_fail(ft_recv_complete_cb
);
602 g_return_if_fail(ft_send_accept_cb
);
603 g_return_if_fail(ft_send_start_cb
);
604 g_return_if_fail(ft_send_cancel_cb
);
605 g_return_if_fail(ft_send_complete_cb
);
606 g_return_if_fail(ft_read_cb
);
607 g_return_if_fail(ft_write_cb
);
608 g_return_if_fail(ft_data_not_sent_cb
);
610 // This assumes thrasher_wrapper_init was already called.
611 g_return_if_fail(callback_funcs
);
613 validate_callback(ft_recv_request_cb
);
614 validate_callback(ft_recv_accept_cb
);
615 validate_callback(ft_recv_start_cb
);
616 validate_callback(ft_recv_cancel_cb
);
617 validate_callback(ft_recv_complete_cb
);
618 validate_callback(ft_send_accept_cb
);
619 validate_callback(ft_send_start_cb
);
620 validate_callback(ft_send_cancel_cb
);
621 validate_callback(ft_send_complete_cb
);
622 validate_callback(ft_read_cb
);
623 validate_callback(ft_write_cb
);
624 validate_callback(ft_data_not_sent_cb
);
634 ft_callback
.ft_recv_request_cb
= newSVsv(ft_recv_request_cb
);
635 ft_callback
.ft_recv_accept_cb
= newSVsv(ft_recv_accept_cb
);
636 ft_callback
.ft_recv_start_cb
= newSVsv(ft_recv_start_cb
);
637 ft_callback
.ft_recv_cancel_cb
= newSVsv(ft_recv_cancel_cb
);
638 ft_callback
.ft_recv_complete_cb
= newSVsv(ft_recv_complete_cb
);
639 ft_callback
.ft_send_accept_cb
= newSVsv(ft_send_accept_cb
);
640 ft_callback
.ft_send_start_cb
= newSVsv(ft_send_start_cb
);
641 ft_callback
.ft_send_cancel_cb
= newSVsv(ft_send_cancel_cb
);
642 ft_callback
.ft_send_complete_cb
= newSVsv(ft_send_complete_cb
);
643 ft_callback
.ft_read_cb
= newSVsv(ft_read_cb
);
644 ft_callback
.ft_write_cb
= newSVsv(ft_write_cb
);
645 ft_callback
.ft_data_not_sent_cb
= newSVsv(ft_data_not_sent_cb
);
657 * @brief Validate then call the given callback pointer.
658 * @param key a hash key for the local function callback table.
659 * @return boolean as int (0 || 1) on success
661 int func_call(gpointer key
)
663 /* @exception key cannot be NULL */
664 g_return_val_if_fail(key
!= NULL
,TFALSE
);
666 gpointer orig_key
= NULL
;
667 hash_record
*record
= NULL
;
668 int (*timeout_add_caller
)(gpointer
);
669 int (*input_add_caller
)(gpointer
, gint
, PurpleInputCondition
);
672 /* Crazy voodoo madness!
673 * We're using direct pointers as keys and a struct at the
674 * end of the pointer, so no need to get any values.
676 if (g_hash_table_lookup_extended(callback_funcs
, key
, &orig_key
, NULL
))
680 if (record
->type
== TIMEOUT_ADD
)
682 timeout_add_caller
= record
->function
;
683 ret
= (timeout_add_caller
)(record
->data
);
686 else if (record
->type
== INPUT_ADD
)
688 input_add_caller
= record
->function
;
689 (input_add_caller
)(record
->data
, record
->fd
,
691 // these functions are void
697 /* We're never gonna be calling this again, so get rid of it */
698 g_hash_table_remove(callback_funcs
, record
);
706 purple_debug_info("thrasher", "Callback for [%p] does not exist!\n", key
);
714 * @brief Wraps Glib::Source->remove
715 * @param key to return to the Perl end
716 * @return Success of call, including the success of the callback
718 gboolean
thrasher_wrapper_call_source_remove (long key
)
722 /* @exception key cannot be NULL */
723 g_return_val_if_fail(callback
.source_remove
, 0);
725 /* @exception callback.source_remove cannot be NULL */
726 g_return_val_if_fail(key
!= TNULL
, 0);
735 /* Push the key onto the stack */
736 XPUSHs(sv_2mortal(newSViv(key
)));
740 call_sv(callback
.source_remove
, G_EVAL
| G_SCALAR
);
751 /* Remove call from lookup table */
752 g_hash_table_remove(perl_key_lookup
, (gpointer
)&key
);
759 * PurpleInputCondition is a bitwise OR of read/write events
760 * PurpleInputFunction is a callback to a guint function with the args gpointer, gint, and PurpleInoutCondition
763 * @brief Wraps Glib::IO->add_watch
764 * @param File descriptor
765 * @param Condition to trigger on
766 * @param Nested callback
767 * @param Data to pass to callback
770 long thrasher_wrapper_set_input_add (guint fd
, PurpleInputCondition cond
, PurpleInputFunction function
, gpointer data
)
772 g_return_val_if_fail(callback
.input_add
, 0);
773 g_return_val_if_fail(function
!= NULL
, 0);
777 hash_record
*record
= (hash_record
*)malloc(sizeof(hash_record
));
779 record
->type
= INPUT_ADD
;
780 record
->function
= function
;
785 g_hash_table_insert(callback_funcs
, record
, NULL
);
787 key
= PTR_TO_LONG(record
);
788 g_return_val_if_fail(key
!= 0, 0);
801 svkey
= newSViv(key
);
802 temp_cv
= get_cv(trigger_input_func_name
, FALSE
);
806 pc
= newRV((SV
*)temp_cv
);
808 /* Ok, this is hairy, first we push the vars we want to send to the
809 * timeout_add Perl callback. Next we push the var we want to give
810 * back to ourselves via trigger_input_func.
815 XPUSHs(sv_2mortal(newSViv(fd
)));
816 XPUSHs(sv_2mortal(newSViv(cond
)));
817 XPUSHs(sv_2mortal(pc
));
818 XPUSHs(sv_2mortal(svkey
));
822 call_sv(callback
.input_add
, G_EVAL
| G_SCALAR
);
825 /* Tweak our internal key to an external key */
830 croak("We've got serious problems, cannot create CV* to [%s]", trigger_input_func_name
);
833 g_hash_table_insert(perl_key_lookup
, (gpointer
)&key
, record
);
840 * @brief Wraps Glib::Timeout->add
841 * @param interval in milliseconds
843 * @param data to pass to the callback
844 * @return Key for the internal timeout_add function hash. (Should this really be returning anything?!)
846 long thrasher_wrapper_set_timeout_add (guint interval
, GSourceFunc function
, gpointer data
)
848 /* @exception callback.timeout_add cannot be NULL */
849 g_return_val_if_fail(callback
.timeout_add
, 0);
850 g_return_val_if_fail(function
!= NULL
, TFALSE
);
854 hash_record
*record
= (hash_record
*)malloc(sizeof(hash_record
*));
856 record
->type
= TIMEOUT_ADD
;
857 record
->function
= function
;
860 g_hash_table_insert(callback_funcs
, record
, NULL
);
862 key
= PTR_TO_LONG(record
);
863 g_return_val_if_fail(key
!= 0, 0);
875 svkey
= newSViv(key
);
876 temp_cv
= get_cv(trigger_timeout_func_name
, FALSE
);
880 pc
= newRV((SV
*)temp_cv
);
882 /* Ok, this is hairy, first we push the vars we want to send to the
883 * timeout_add Perl callback. Next we push the var we want to give
884 * back to ourselves via trigger_input_func.
889 XPUSHs(sv_2mortal(newSViv(interval
)));
890 XPUSHs(sv_2mortal(pc
));
891 XPUSHs(sv_2mortal(svkey
));
895 call_sv(callback
.timeout_add
, G_EVAL
| G_SCALAR
);
899 /* Tweak our internal key to an external key */
904 croak("We've got serious problems, cannot create CV* to [%s]", trigger_input_func_name
);
918 * @brief Send a @p message out via callback.incoming_msg
919 * @param jid user the @p message is being sent to
920 * @param sender user name of the @p message sender
921 * @param alias alternate user name of the @p sender (may be NULL)
922 * @param message string being sent to @p jid
923 * @return 1 on success, 0 on failure
925 int thrasher_wrapper_incoming_msg (const char *jid
,
932 /* @exception jid, sender, and message cannot be NULL */
933 g_return_val_if_fail(jid
!= NULL
, 0);
934 g_return_val_if_fail(sender
!= NULL
, 0);
935 g_return_val_if_fail(message
!= NULL
, 0);
937 /* @exception bail if our callback doesn't exist */
938 g_return_val_if_fail(callback
.incoming_msg
!= NULL
, 0);
947 /* Push the args onto the stack */
948 XPUSHs(sv_2mortal(newSVpvn(jid
, strlen(jid
))));
949 XPUSHs(sv_2mortal(newSVpvn(sender
, strlen(sender
))));
950 XPUSHs(sv_2mortal(newSVpvn(alias
, strlen(alias
))));
951 XPUSHs(sv_2mortal(newSVpvn(message
, strlen(message
))));
955 call_sv(callback
.incoming_msg
, G_EVAL
| G_SCALAR
);
970 * @brief Forwards the 'connection' event, that authentication has
972 * @param jid user who has successfully connected
974 int thrasher_wrapper_connection(const char *jid
)
978 /* @exception jid, sender, and message cannot be NULL */
979 g_return_val_if_fail(jid
!= NULL
, 0);
981 /* @exception bail if our callback doesn't exist */
982 g_return_val_if_fail(callback
.connection
!= NULL
, 0);
991 /* Push the args onto the stack */
992 XPUSHs(sv_2mortal(newSVpvn(jid
, strlen(jid
))));
996 call_sv(callback
.connection
, G_EVAL
| G_SCALAR
);
1010 SV
*clean_newSVpv(const char *inp
)
1013 return sv_2mortal(newSVpv(inp
, 0));
1015 return sv_2mortal(&PL_sv_undef
);
1020 * @brief Forward presence information via callback.presence_in
1021 * @param jid user the @p status is being sent to
1022 * @param sender user name of the @p status sender
1023 * @param alias alternate user name of the @p sender (may be NULL)
1024 * @param group which the @p sender belongs to
1025 * @param status PurpleStatusPrimitive (guint) being sent to @p jid
1026 * @param message string being sent to @p jid (may be NULL)
1027 * @return 1 on success, 0 on failure (wrapped Perl)
1029 int thrasher_wrapper_presence_in(const char *jid
,
1034 const char *message
)
1038 /* @exception jid, sender, and group cannot be NULL */
1039 g_return_val_if_fail(jid
!= NULL
, 0);
1040 g_return_val_if_fail(sender
!= NULL
, 0);
1041 g_return_val_if_fail(group
!= NULL
, 0);
1043 /* @exception bail if our callback doesn't exist */
1044 g_return_val_if_fail(callback
.presence_in
!= NULL
, 0);
1053 /* Push the args onto the stack */
1054 XPUSHs(clean_newSVpv(jid
));
1055 XPUSHs(clean_newSVpv(sender
));
1056 XPUSHs(clean_newSVpv(alias
));
1057 XPUSHs(clean_newSVpv(group
));
1058 XPUSHs(sv_2mortal(newSViv(status
)));
1059 XPUSHs(clean_newSVpv(message
));
1063 call_sv(callback
.presence_in
, G_EVAL
| G_SCALAR
);
1077 int thrasher_wrapper_legacy_user_add_user(const char *jid
,
1078 const char *sender
) {
1081 g_return_val_if_fail(jid
, 0);
1082 g_return_val_if_fail(sender
, 0);
1084 g_return_val_if_fail(callback
.legacy_user_add_user
, 0);
1093 XPUSHs(clean_newSVpv(jid
));
1094 XPUSHs(clean_newSVpv(sender
));
1098 call_sv(callback
.legacy_user_add_user
, G_EVAL
| G_SCALAR
);
1113 * @brief Forward subscription request information via callback.subscription_add
1114 * @param jid user the @p status is being sent to
1115 * @param sender user name of the requester
1116 * @param status PurpleStatusPrimitive (guint) being sent to @p jid
1117 * @return 1 on success, 0 on failure (wrapped Perl)
1119 int thrasher_wrapper_subscription_add(const char *jid
,
1125 /* @exception jid, sender, and group cannot be NULL */
1126 g_return_val_if_fail(jid
!= NULL
, 0);
1127 g_return_val_if_fail(sender
!= NULL
, 0);
1129 /* @exception bail if our callback doesn't exist */
1130 g_return_val_if_fail(callback
.subscription_add
!= NULL
, 0);
1139 /* Push the args onto the stack */
1140 XPUSHs(clean_newSVpv(jid
));
1141 XPUSHs(clean_newSVpv(sender
));
1142 XPUSHs(sv_2mortal(newSViv(status
)));
1146 call_sv(callback
.subscription_add
, G_EVAL
| G_SCALAR
);
1161 * @brief Forward connection error messages via callback.connection_error
1162 * @param jid user the @p error_code is being sent to
1163 * @param error_code PurpleConnectionError (guint) being sent to @p jid
1164 * @param message a error message (may be NULL)
1165 * @return 1 on success, 0 on failure (wrapped Perl)
1167 int thrasher_wrapper_connection_error(const char *jid
,
1168 const guint error_code
,
1169 const char *message
)
1173 // If the JID is null, pick it up off of
1174 // thrasher.c:current_login_jid, per the comment in that file.
1176 purple_debug_info("thrasher", "had to retrieve the jid from mem\n");
1177 jid
= get_current_login_jid();
1180 // If we're in the middle of a connection, pick up that we have an error.
1183 g_return_val_if_fail(jid
, 0);
1186 /* @exception error_code cannot be less than zero or greater than PURPLE_CONNECTION_ERROR_OTHER_ERROR */
1187 g_return_val_if_fail(error_code
>= 0, ret
);
1188 g_return_val_if_fail(error_code
<= PURPLE_CONNECTION_ERROR_OTHER_ERROR
, ret
);
1197 /* Push the args onto the stack */
1198 XPUSHs(clean_newSVpv(jid
));
1199 XPUSHs(sv_2mortal(newSViv(error_code
)));
1200 XPUSHs(clean_newSVpv(message
));
1204 call_sv(callback
.connection_error
, G_EVAL
| G_SCALAR
);
1215 // Remove the account from our current active set
1216 thrasher_remove_account(jid
);
1221 void thrasher_remove_account (const char *jid
)
1223 PurpleAccount
*account
;
1225 g_return_if_fail(jid
!= NULL
);
1227 purple_debug_info("thrasher", "Removing active account for %s\n",
1230 account
= g_hash_table_lookup(accts
, jid
);
1231 if (account
!= NULL
)
1233 g_hash_table_remove(accts
, jid
);
1238 * @brief Forwards typing notification state changes from libpurple buddies.
1239 * @param pa Account receiving the notification
1240 * @param who Buddy name sending the notification
1243 void thrasher_wrapper_incoming_chatstate(PurpleAccount
* pa
,
1245 PurpleTypingState state
) {
1246 /* @exception pa and who cannot be NULL */
1247 g_return_if_fail(pa
!= NULL
);
1248 g_return_if_fail(who
!= NULL
);
1250 /* @exception bail if our callback doesn't exist */
1251 g_return_if_fail(callback
.incoming_chatstate
!= NULL
);
1253 gchar
* jid
= thrasher_account_get_jid(pa
);
1254 /* @exception bail if account has no JID */
1255 g_return_if_fail(jid
!= NULL
);
1264 /* Push the args onto the stack */
1265 XPUSHs(clean_newSVpv(jid
));
1266 XPUSHs(clean_newSVpv(who
));
1267 XPUSHs(sv_2mortal(newSViv(state
)));
1271 call_sv(callback
.incoming_chatstate
, G_EVAL
| G_SCALAR
);
1282 * @begin External trigger to flush the internal function tables
1284 void thrasher_wrapper_destructor ()
1286 g_hash_table_destroy(callback_funcs
);
1287 g_hash_table_destroy(perl_key_lookup
);
1291 thrasher_wrapper_send_file(const char *jid
,
1296 g_return_val_if_fail(jid
, 0);
1297 g_return_val_if_fail(who
, 0);
1299 purple_debug_info("thrasher ft",
1300 "Sending file %s -> %s\n",
1303 PurpleAccount
*account
= g_hash_table_lookup(accts
, jid
);
1305 g_return_val_if_fail(account
, 0);
1307 return thrasher_send_file(account
, who
, filename
, size
, desc
);
1311 thrasher_action_ft_ui_ready(size_t id
) {
1312 PurpleXfer
* xfer
= get_xfer_by_id(id
);
1313 g_return_if_fail(xfer
);
1314 purple_xfer_ui_ready(xfer
);
1315 /* HACK: READY_PRPL is usually not set (or was cleared) right now,
1316 * and some prpls don't appear to set it correctly on repeats.
1317 * Spam prpl_ready to force a do_transfer anyway.
1319 * Using a separate watch on the remote xfer->fd would avoid some
1320 * EAGAINs. I'd be happy to be proven wrong, but testing found no
1321 * significant benefit to Thrasher in that.
1323 purple_xfer_prpl_ready(xfer
);
1327 * @brief Called on libpurple file-send-start signal
1328 * @param id of Thrasher file transfer structure.
1331 thrasher_wrapper_ft_send_start(guint id
) {
1334 /* @exception bail if our callback doesn't exist */
1335 g_return_val_if_fail(ft_callback
.ft_send_start_cb
!= NULL
, 0);
1344 /* Push the args onto the stack */
1345 XPUSHs(sv_2mortal(newSViv(id
)));
1349 call_sv(ft_callback
.ft_send_start_cb
, G_EVAL
| G_SCALAR
);
1364 * @brief Called on libpurple file-send-cancel signal
1365 * @param id of Thrasher file transfer structure.
1368 thrasher_wrapper_ft_send_cancel(guint id
) {
1369 /* @exception bail if our callback doesn't exist */
1370 g_return_if_fail(ft_callback
.ft_send_cancel_cb
!= NULL
);
1379 /* Push the args onto the stack */
1380 XPUSHs(sv_2mortal(newSViv(id
)));
1384 call_sv(ft_callback
.ft_send_cancel_cb
, G_EVAL
| G_SCALAR
);
1397 * @brief Called on libpurple file-send-complete signal
1398 * @param id of Thrasher file transfer structure.
1401 thrasher_wrapper_ft_send_complete(guint id
) {
1402 /* @exception bail if our callback doesn't exist */
1403 g_return_if_fail(ft_callback
.ft_send_complete_cb
!= NULL
);
1412 /* Push the args onto the stack */
1413 XPUSHs(sv_2mortal(newSViv(id
)));
1417 call_sv(ft_callback
.ft_send_complete_cb
, G_EVAL
| G_SCALAR
);
1430 thrasher_wrapper_ft_read(guint id
,
1436 /* @exception bail if our callback doesn't exist */
1437 g_return_val_if_fail(ft_callback
.ft_read_cb
!= NULL
, 0);
1446 /* Push the args onto the stack */
1447 XPUSHs(sv_2mortal(newSViv(id
)));
1448 XPUSHs(sv_2mortal(newSViv(size
)));
1452 count
= call_sv(ft_callback
.ft_read_cb
, G_EVAL
| G_ARRAY
);
1458 void* read
= POPpbytex
;
1459 *buffer
= malloc(read_sz
);
1460 /* This buffer is free'd by libpurple. */
1461 memcpy(*buffer
, read
, read_sz
);
1477 thrasher_wrapper_ft_data_not_sent(guint id
,
1478 const guchar
*buffer
,
1480 /* @exception buffer cannot be NULL */
1481 g_return_if_fail(buffer
!= NULL
);
1483 /* @exception bail if our callback doesn't exist */
1484 g_return_if_fail(ft_callback
.ft_data_not_sent_cb
!= NULL
);
1493 /* Push the args onto the stack */
1494 XPUSHs(sv_2mortal(newSViv(id
)));
1495 /* buffer is not null-terminated. */
1496 XPUSHs(sv_2mortal(newSVpvn(buffer
, size
)));
1500 call_sv(ft_callback
.ft_data_not_sent_cb
, G_EVAL
| G_SCALAR
);
1513 * @brief ask user's permission for ft and set up bytestream
1514 * @param xfer a filled-in PURPLE_XFER_RECEIVE PurpleXfer.
1515 * @param filename suggested local filename
1516 * @return nothing; use thrasher_action_ft_recv_request_respond when ready
1519 thrasher_wrapper_ft_recv_request(PurpleXfer
*xfer
,
1520 const char* filename
) {
1521 /* @exception xfer cannot be NULL */
1522 g_return_if_fail(xfer
!= NULL
);
1524 /* @exception bail if our callback doesn't exist */
1525 g_return_if_fail(ft_callback
.ft_recv_request_cb
!= NULL
);
1534 /* Push the args onto the stack */
1535 guint id
= get_id_by_xfer(xfer
);
1536 XPUSHs(sv_2mortal(newSViv(id
)));
1537 PurpleAccount
* pa
= purple_xfer_get_account(xfer
);
1538 gchar
* jid
= thrasher_account_get_jid(pa
);
1539 XPUSHs(clean_newSVpv(jid
));
1540 const char* who
= purple_xfer_get_remote_user(xfer
);
1541 XPUSHs(clean_newSVpv(who
));
1542 XPUSHs(clean_newSVpv(filename
));
1543 size_t size
= purple_xfer_get_size(xfer
);
1544 XPUSHs(sv_2mortal(newSViv(size
)));
1548 call_sv(ft_callback
.ft_recv_request_cb
, G_EVAL
| G_SCALAR
);
1561 thrasher_action_ft_recv_request_respond(size_t id
,
1562 unsigned int accept
) {
1563 thrasher_xfer_recv_request_responder(id
, accept
);
1567 thrasher_wrapper_ft_write(guint id
,
1568 const guchar
*buffer
,
1570 int written_sz
= -1;
1572 /* @exception bail if our callback doesn't exist */
1573 g_return_val_if_fail(ft_callback
.ft_write_cb
!= NULL
, 0);
1582 /* Push the args onto the stack */
1583 XPUSHs(sv_2mortal(newSViv(id
)));
1584 /* buffer is not null-terminated. */
1585 XPUSHs(sv_2mortal(newSVpvn(buffer
, size
)));
1589 call_sv(ft_callback
.ft_write_cb
, G_EVAL
| G_ARRAY
);
1604 * @brief Called on libpurple file-recv-cancel signal
1605 * @param id of Thrasher file transfer structure.
1608 thrasher_wrapper_ft_recv_cancel(guint id
) {
1609 /* @exception bail if our callback doesn't exist */
1610 g_return_if_fail(ft_callback
.ft_recv_cancel_cb
!= NULL
);
1619 /* Push the args onto the stack */
1620 XPUSHs(sv_2mortal(newSViv(id
)));
1624 call_sv(ft_callback
.ft_recv_cancel_cb
, G_EVAL
| G_SCALAR
);
1637 * @brief Called on libpurple file-recv-complete signal
1638 * @param id of Thrasher file transfer structure.
1641 thrasher_wrapper_ft_recv_complete(guint id
) {
1642 /* @exception bail if our callback doesn't exist */
1643 g_return_if_fail(ft_callback
.ft_recv_complete_cb
!= NULL
);
1652 /* Push the args onto the stack */
1653 XPUSHs(sv_2mortal(newSViv(id
)));
1657 call_sv(ft_callback
.ft_recv_complete_cb
, G_EVAL
| G_SCALAR
);
1670 thrasher_action_ft_cancel_local(size_t id
) {
1671 PurpleXfer
* xfer
= get_xfer_by_id(id
);
1672 purple_xfer_cancel_local(xfer
);
1676 /*********************************************************
1678 * Only debugging stuff beyond this point!
1680 *********************************************************/
1681 gboolean
foo_callback (void *data
);
1682 gboolean
foo_callback2 (void *data
);
1683 void foo_callback3 (gpointer data
, gint fd
, PurpleInputCondition cond
);
1684 long thrasher_wrapper_trigger_timeout_add (void);
1685 long thrasher_wrapper_trigger_timeout_add2 (void);
1686 long thrasher_wrapper_trigger_timeout_add3 (void);
1687 long thrasher_wrapper_trigger_input_add (void);
1688 void thrasher_wrapper_dump_calls (void);
1689 void spit_func_info (gpointer key
, gpointer value
, gpointer user_data
);
1693 foo_callback (void *data
)
1695 printf("!!!!!!Calling a callback w/ data [%s]!!!!\n", (char *)data
);
1704 foo_callback2 (void *data
)
1706 printf("will die in [%d] cycles\n", cntr
);
1714 void foo_callback3 (gpointer data
, gint fd
, PurpleInputCondition cond
)
1716 printf("data [%p]\tfd [%d]\tcond [%d]\n", data
, fd
, cond
);
1719 /* Test functions to allow an external caller to trigger internal actions */
1720 long thrasher_wrapper_trigger_timeout_add ()
1722 printf("trigger_timeout_add called\n");
1723 return thrasher_wrapper_set_timeout_add(1234, foo_callback
, "YAY! test data");
1727 long thrasher_wrapper_trigger_timeout_add2 ()
1729 printf("trigger_timeout_add2 called\n");
1730 return thrasher_wrapper_set_timeout_add(1867, foo_callback
, "test data says what?");
1733 long thrasher_wrapper_trigger_timeout_add3 ()
1735 printf("trigger_timeout_add3 called\n");
1736 return thrasher_wrapper_set_timeout_add(200, foo_callback2
, "this shoudl fail shortly");
1739 long thrasher_wrapper_trigger_input_add ()
1741 printf("trigger_input_add called\n");
1743 int fd
= open("/tmp/testfoo", O_RDWR
);
1744 PurpleInputCondition cond
;
1745 cond
= PURPLE_INPUT_WRITE
;
1747 printf("HAMMERTIME\n");
1748 face
= thrasher_wrapper_set_input_add(fd
, cond
, foo_callback3
, "double plus win ungood");
1753 int thrasher_wrapper_trigger_timeout_remove (long key
)
1755 printf("trigger_timeout_remove called\n");
1756 /* We currently have no way to correlate external keys to internal keys... */
1757 return !thrasher_wrapper_call_source_remove(key
);
1761 void spit_func_info (gpointer key
, gpointer value
, gpointer user_data
)
1763 hash_record
*record
;
1764 printf("key [%p]\tvalue [%p]\n", key
, value
);
1768 if (record
->type
== INPUT_ADD
)
1770 printf("\tINPUT_ADD\n\tfunction [%p]\n\tdata [%p]\n\tcond [%d]\n\tfd [%d]\n", record
->function
, record
->data
, record
->cond
, record
->fd
);
1773 else if (record
->type
== TIMEOUT_ADD
)
1776 printf("\tTIMEOUT_ADD\n\tfunction [%p]\n\tdata [%p]\n", record
->function
, record
->data
);
1781 printf("record type [%d] is unrecognized!\n", record
->type
);
1785 void thrasher_wrapper_dump_calls ()
1787 g_hash_table_foreach(callback_funcs
, spit_func_info
, NULL
);
1790 #endif /* TH_DEBUG */