Yahoo username with trailing whitespace -> zombified connection.
[thrasher.git] / thperl.c
blobb65f98ffaaf2de5b05d32c31dad067466cabeb3e
1 /*
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
19 #include <glib.h>
20 #include <stdlib.h>
21 #include <errno.h>
22 #include <string.h>
23 #include "thperl.h"
24 #include "thblist.h"
25 #include "blist.h"
26 #include "thrasher.h"
27 #include "thpresence.h"
28 #include "thconversations.h"
29 #include "thft.h"
30 #include "unistd.h"
32 /* Perl */
33 #include "EXTERN.h"
34 #include "perl.h"
35 #include "XSUB.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);
69 /**
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)
76 int err = 0;
78 #ifdef TH_DEBUG
79 purple_debug_info("thrasher",
80 "Login request:\n");
81 GList *keys = g_hash_table_get_keys(hash_args);
82 GList *values = g_hash_table_get_values(hash_args);
83 while (keys)
85 purple_debug_info("thrasher",
86 " %s -> %s\n",
87 (char *)keys->data,
88 (char *)values->data);
89 keys = keys->next;
90 values = values->next;
93 g_list_free(keys);
94 g_list_free(values);
95 #endif /* TH_DEBUG */
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,
117 jid, hash_args);
119 // In the event of an error, connection_error has already been
120 // called.
121 if (account == NULL) {
122 return 1;
125 char *new_jid = g_strdup(jid);
127 /* Add user to hash */
128 g_hash_table_insert(
129 accts,
130 new_jid, /* Should eventually be the protocol + username */
131 account
134 /* Success! */
135 err = 0;
137 else
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",
142 jid);
143 err = 2;
146 g_free(jid);
147 g_free(username);
148 g_free(password);
149 g_free(proto);
150 g_hash_table_destroy(hash_args);
152 return err;
157 * @brief Get a list of all JIDs with active accounts.
159 GList_String*
160 thrasher_action_get_logged_in_jids() {
161 GList* jids = g_hash_table_get_keys(accts);
162 return jids;
168 * @brief add buddy (@p buddy_name) for given @p jid buddy list
169 * @param jid releational key to bound to account
170 * @param buddy_name name of buddy
171 * @return 1 if there is a problem, 0 otherwise (C)
173 int thrasher_action_buddy_add (char *jid, char *buddy_name)
175 PurpleAccount *account = NULL;
176 PurpleBuddy *buddy = NULL;
177 PurpleGroup *group = NULL;
179 purple_debug_info("thrasher", "Buddy_add request\n");
181 account = g_hash_table_lookup(accts, jid);
182 g_return_val_if_fail(account != NULL, 1);
184 buddy = purple_find_buddy(account, buddy_name);
186 /* Re-requesting authorization addresses cases in which a buddy is
187 * implicitly added somewhere else but never actually sent an
188 * authorization request. Such a state was sometimes possible with
189 * Yahoo but consistently reproducible with as follows in ICQ:
191 * 1. Register user1 and user2 with the ICQ transport, each on the
192 * other's roster.
193 * 2. Remove user1's ICQ contact from user2's roster.
194 * 3. Remove user2's ICQ contact from user1's roster.
195 * 4. Add user2's ICQ contact to user1's roster.
196 * 5. thrasher_buddy_request_authorize() receives and handles an
197 * authorization request from user1 on behalf of user2. It
198 * adds user1 to user2's buddy list.
199 * 6. user1 is now fully authorized with user2.
200 * 7. user2, however, had user1 added as an *un*authorized buddy.
202 * This also means Thrasher tries to do something sensible with
203 * XMPP client operations like "re-request authorization from
204 * contact". Otherwise, they'd always be handled as no-ops because
205 * the libpurple buddy is already present.
207 gboolean need_auth;
208 if (buddy) {
209 /* Re-add: check if (re-)auth is needed. */
210 need_auth = ! thrasher_account_buddy_is_authorized(account, buddy);
212 else {
213 /* Not yet on list: assume an auth request is needed. */
214 need_auth = 1;
216 /* FIXME: There's no cross-prpl calls for checking/sending
217 * authorization like oscar.c:purple_auth_request()? Could try to
218 * parse for the menu items added by each prpl we know requires
219 * authorization. :(
221 if (need_auth) {
222 purple_debug_info("thrasher",
223 "%s needs authorization from %s\n",
224 jid, buddy_name);
227 // FIXME: Figure out if we need this buddy list stuff
228 // at all. We don't actually care, as long as we get
229 // the messages and presence to send out.
231 /* We're done if buddy already exists and is authorized.
233 * libpurple says prpl should interpret a re-add of a existent
234 * buddy as an attempt to (re)send an authorization request if
235 * applicable.
237 if (buddy == NULL || need_auth)
239 char *default_group = "General";
241 /* Check to see if default group exists */
242 group = purple_find_group(default_group);
244 /* If not, create default group */
245 if (group == NULL)
246 group = purple_group_new(default_group);
248 purple_blist_add_group(group, NULL);
250 /* Add buddy to default group (NULL should be alias...) */
251 buddy = purple_buddy_new(account, buddy_name, NULL);
252 purple_blist_add_buddy(buddy, NULL, group, NULL);
253 purple_account_add_buddy(account, buddy);
255 purple_debug_info("thrasher",
256 "finished adding %s to %s's buddy list\n",
257 buddy_name, jid);
259 return 0;
260 } else {
261 purple_debug_info("thrasher",
262 "%s already on %s's buddy list\n",
263 buddy_name, jid);
266 /* Buddy already exists */
267 return 1;
271 * @brief remove buddy (@p buddy_name) from given @p jid buddy list
272 * @param jid releational key to bound to account
273 * @param buddy_name name of buddy
274 * @return 1 if there is a problem, 0 otherwise (C)
276 int thrasher_action_buddy_remove (char *jid, char *buddy_name)
278 PurpleGroup *group = NULL;
279 PurpleBuddy *buddy = NULL;
280 PurpleAccount *account = NULL;
282 purple_debug_info("thrasher", "Buddy remove\n");
284 account = g_hash_table_lookup(accts, jid);
285 g_return_val_if_fail(account != NULL, 1);
287 // Don't try and remove a buddy if the account
288 // isn't connected
289 if (purple_account_is_connected(account)) {
291 purple_debug_info("thrasher", "find_buddy...\n");
292 buddy = purple_find_buddy(account, buddy_name);
294 /* Only remove a buddy if they exist */
295 if (buddy != NULL)
297 purple_debug_info("thrasher", "get_group...\n");
298 /* Give up us the group */
299 group = purple_buddy_get_group(buddy);
301 purple_debug_info("thrasher", "account_remove_buddy...\n");
302 /* Remove server-side buddy entry */
303 purple_account_remove_buddy(account, buddy, group);
305 purple_debug_info("thrasher", "blist_remove_buddy...\n");
306 /* Also need to remove buddy entry from memory */
307 purple_blist_remove_buddy(buddy);
309 return 0;
313 /* Account isn't connected or buddy is not found */
314 return 1;
317 void thrasher_action_buddy_authorize(char *jid, char *legacy_username)
319 thrasher_action_buddy_add(jid, legacy_username);
322 void thrasher_action_buddy_deauthorize(char *jid, char *legacy_username) {
323 thrasher_action_buddy_remove(jid, legacy_username);
327 * @brief update presence status for given @p jid
328 * @param jid releational key to bound to account
329 * @param status integer representation of PurpleStatusPrimitive
330 * @param message a status message (may be NULL)
331 * @return 1 if there is a problem, 0 otherwise (C)
333 int thrasher_action_presence (char *jid, int status, char *message)
335 PurpleAccount *account;
337 g_return_val_if_fail(jid != NULL, 0);
339 account = g_hash_table_lookup(accts, jid);
341 if (account == NULL)
342 return 1;
344 thrasher_set_presence(account, status, message);
346 return 0;
351 * @brief Send @p message from the user given by @p jid to the user @p recipient
352 * @param jid
353 * @param recipient
354 * @param message
355 * @return 1 on success 0 on failure
357 int thrasher_action_outgoing_msg (char *jid, char *recipient, char *message)
359 int ret = 0;
360 PurpleAccount *account = NULL;
362 g_return_val_if_fail(jid != NULL, 0);
363 g_return_val_if_fail(recipient != NULL, 0);
364 g_return_val_if_fail(message != NULL, 0);
366 purple_debug_info("thrasher", "Send message request\n");
367 /* Pull in user if they exist */
368 account = g_hash_table_lookup(accts, jid);
370 /* User does not exist (may not be logged in) */
371 if (account == NULL) {
372 return 0;
375 /* Try to send message (message might not be sent) */
376 if (thrasher_send_msg(account,recipient,message) == TRUE)
377 ret = 1;
379 return ret;
384 * @brief Send @p chatstate from the user given by @p jid to the @p recipient
385 * @param jid
386 * @param recipient
387 * @param chatstate
389 void
390 thrasher_action_outgoing_chatstate(char *jid,
391 char *recipient,
392 PurpleTypingState chatstate) {
393 g_return_if_fail(jid != NULL);
394 g_return_if_fail(recipient != NULL);
396 PurpleAccount *pa = NULL;
397 pa = g_hash_table_lookup(accts, jid);
398 if (pa == NULL) {
399 /* User does not exist (may not be logged in) */
400 return;
403 /* Try to send chatstate */
404 thrasher_outgoing_chatstate(pa, recipient, chatstate);
409 * @brief Log the user out with the given jid
410 * @param jid releational key to bind to account
411 * @return 1 if logout was successful, 0 if it was not
413 * NOTE: As we're going to be running a process for each protocol,
414 * there's no need to worry about the protocol used.
416 int thrasher_action_logout (char *jid)
418 int ret = 0;
419 PurpleAccount *account;
421 g_return_val_if_fail(jid != NULL, 0);
423 purple_debug_info("thrasher", "Logout requested for %s\n", jid);
425 /* Verify user is logged in */
426 account =g_hash_table_lookup(accts, jid);
427 if (account != NULL)
429 thrasher_logout(account);
430 thrasher_remove_account(jid);
431 ret = 1;
434 return ret;
438 void _thrasher_action_debug_logged_in_accts_printer(gpointer key, gpointer value, gpointer user_data);
441 * @brief Dump active account name lists to STDERR from libpurple and thperl.
443 void
444 thrasher_action_debug_logged_in() {
445 fprintf(stderr, "purple_accounts_get_all_active:\n");
446 GList* list = purple_accounts_get_all_active();
447 if (list) {
448 GList *l = list;
449 while (l) {
450 PurpleAccount *pa = (PurpleAccount*)l->data;
451 fprintf(stderr, "\t%s = %p\n", purple_account_get_username(pa), pa);
452 l = l->next;
454 g_list_free(list);
457 if (accts) {
458 fprintf(stderr, "thperl accts:\n");
459 g_hash_table_foreach(accts,
460 _thrasher_action_debug_logged_in_accts_printer,
461 NULL);
463 else {
464 fprintf(stderr, "No thperl accts?!!\n");
468 void
469 _thrasher_action_debug_logged_in_accts_printer(gpointer key,
470 gpointer value,
471 gpointer user_data) {
472 const char *jid = (char*)key;
473 PurpleAccount *pa = (PurpleAccount*)value;
474 fprintf(stderr, "\t%s = %s = %p\n",
475 jid, purple_account_get_username(pa), pa);
480 * @brief Destruction function for our internal function hash
481 * @param key data element
483 void destroy_record_key (gpointer key)
485 free(key);
490 * @brief Bizarro passthrough to allow perl calling of dynamically-set C events
491 * @param key a guint value corresponding to the pointer of a callback.
492 * @return boolean as int (0 || 1) on success. Returns 0 on failure as well.
494 int thrasher_wrapper_trigger_timeout_func(long key)
496 /* @exception key cannot be NULL */
497 g_return_val_if_fail(key != TNULL,TFALSE);
499 char *real_key = LONG_TO_PTR(key);
501 return func_call(real_key);
505 * @brief Bizarro passthrough (two) to allow perl calling of dynamically-set C events
506 * @param file descriptor
507 * @param condition trigger was called on
508 * @param key a long value corresponding to the pointer of a callback.
509 * @return boolean as int (0 || 1) on success. Returns 0 on failure as well.
511 int thrasher_wrapper_trigger_input_func(long fd, SV *cond, long key)
513 /* @exception fd cannot be zero */
514 g_return_val_if_fail(fd != 0, 0);
515 /* @exception cond cannot be zero */
516 g_return_val_if_fail(cond != 0, 0);
517 /* @exception key cannot be zero */
518 g_return_val_if_fail(key != 0, 0);
520 // GIOCondition real_cond = cond;
521 char *real_key = LONG_TO_PTR(key);
523 /* FIXME: We need to pass cond and key to this call... */
524 return func_call(real_key);
528 * @brief Simple verifcation for callbacks
529 * @param subref to callback
532 void validate_callback (SV *cb)
534 if (!SvROK(cb) || SvTYPE(SvRV(cb)) != SVt_PVCV)
536 croak("Not a callback!");
537 exit(0);
543 * @brief Initialize our wrapper
544 * @param subref (SV *) to call for timeout_add
545 * @param subref (SV *) to call for input_add
546 * @param subref (SV *) to call for source_remove
547 * @param subref (SV *) to call on incoming messages
548 * @param subref (SV *) to call on incoming presence information
549 * @param subref (SV *) to call on subscription requests
550 * @param subref (SV *) to call on subscription requests from legacy users
551 * @param subref (SV *) to call on connection errors
552 * @param subref (SV *) to call when connections have succeeded
553 * @param subref (SV *) to call on incoming chatstate (optional)
555 void thrasher_wrapper_init (SV *timeout_add_cb,
556 SV *input_add_cb,
557 SV *source_remove_cb,
558 SV *incoming_msg_cb,
559 SV *presence_in_cb,
560 SV *subscription_add_cb,
561 SV *legacy_user_add_user_cb,
562 SV *connection_error_cb,
563 SV *connection_cb,
564 ...)
566 va_list argp;
567 va_start(argp, connection_cb);
568 SV* incoming_chatstate_cb = va_arg(argp, SV*);
570 g_return_if_fail(timeout_add_cb);
571 g_return_if_fail(input_add_cb);
572 g_return_if_fail(source_remove_cb);
573 g_return_if_fail(incoming_msg_cb);
574 g_return_if_fail(presence_in_cb);
575 g_return_if_fail(subscription_add_cb);
576 g_return_if_fail(legacy_user_add_user_cb);
577 g_return_if_fail(connection_error_cb);
578 g_return_if_fail(connection_cb);
580 /* Set the bindings for our function table */
581 callback_funcs = g_hash_table_new_full(g_direct_hash,
582 g_direct_equal,
583 destroy_record_key,
584 NULL);
586 /* Create the lookup table so we know what to do when Perl triggers a remove */
587 perl_key_lookup = g_hash_table_new(g_int_hash, g_int_equal);
589 /* Validate callback, this burns up if it fails so no
590 * need to check for errors!
592 validate_callback(timeout_add_cb);
593 validate_callback(input_add_cb);
594 validate_callback(source_remove_cb);
595 validate_callback(incoming_msg_cb);
596 validate_callback(presence_in_cb);
597 validate_callback(subscription_add_cb);
598 validate_callback(legacy_user_add_user_cb);
599 validate_callback(connection_error_cb);
600 validate_callback(connection_cb);
601 if (incoming_chatstate_cb) {
602 validate_callback(incoming_chatstate_cb);
605 dSP;
607 ENTER;
608 SAVETMPS;
610 PUSHMARK(SP);
611 PUTBACK;
613 callback.timeout_add = newSVsv(timeout_add_cb);
614 callback.input_add = newSVsv(input_add_cb);
615 callback.source_remove = newSVsv(source_remove_cb);
616 callback.incoming_msg = newSVsv(incoming_msg_cb);
617 callback.presence_in = newSVsv(presence_in_cb);
618 callback.subscription_add = newSVsv(subscription_add_cb);
619 callback.legacy_user_add_user = newSVsv(legacy_user_add_user_cb);
620 callback.connection_error = newSVsv(connection_error_cb);
621 callback.connection = newSVsv(connection_cb);
622 if (incoming_chatstate_cb) {
623 callback.incoming_chatstate = newSVsv(incoming_chatstate_cb);
625 else {
626 callback.incoming_chatstate = 0;
629 SPAGAIN;
630 PUTBACK;
632 FREETMPS;
633 LEAVE;
637 * @brief Initialize our file transfer wrapper
638 * @param subref (SV *) to call for recv_accept
639 * @param subref (SV *) to call for recv_start
640 * @param subref (SV *) to call for recv_cancel
641 * @param subref (SV *) to call for recv_complete
642 * @param subref (SV *) to call for send_accept
643 * @param subref (SV *) to call for send_start
644 * @param subref (SV *) to call for send_cancel
645 * @param subref (SV *) to call for send_complete
646 * @param subref (SV *) to call for ui_read. Must return (buffer, size).
647 * @param subref (SV *) to call for ui_write
648 * @param subref (SV *) to call for ui_data_not_sent
650 void thrasher_wrapper_ft_init(SV *ft_recv_request_cb,
651 SV *ft_recv_accept_cb,
652 SV *ft_recv_start_cb,
653 SV *ft_recv_cancel_cb,
654 SV *ft_recv_complete_cb,
655 SV *ft_send_accept_cb,
656 SV *ft_send_start_cb,
657 SV *ft_send_cancel_cb,
658 SV *ft_send_complete_cb,
659 SV *ft_read_cb,
660 SV *ft_write_cb,
661 SV *ft_data_not_sent_cb)
663 g_return_if_fail(ft_recv_request_cb);
664 g_return_if_fail(ft_recv_accept_cb);
665 g_return_if_fail(ft_recv_start_cb);
666 g_return_if_fail(ft_recv_cancel_cb);
667 g_return_if_fail(ft_recv_complete_cb);
668 g_return_if_fail(ft_send_accept_cb);
669 g_return_if_fail(ft_send_start_cb);
670 g_return_if_fail(ft_send_cancel_cb);
671 g_return_if_fail(ft_send_complete_cb);
672 g_return_if_fail(ft_read_cb);
673 g_return_if_fail(ft_write_cb);
674 g_return_if_fail(ft_data_not_sent_cb);
676 // This assumes thrasher_wrapper_init was already called.
677 g_return_if_fail(callback_funcs);
679 validate_callback(ft_recv_request_cb);
680 validate_callback(ft_recv_accept_cb);
681 validate_callback(ft_recv_start_cb);
682 validate_callback(ft_recv_cancel_cb);
683 validate_callback(ft_recv_complete_cb);
684 validate_callback(ft_send_accept_cb);
685 validate_callback(ft_send_start_cb);
686 validate_callback(ft_send_cancel_cb);
687 validate_callback(ft_send_complete_cb);
688 validate_callback(ft_read_cb);
689 validate_callback(ft_write_cb);
690 validate_callback(ft_data_not_sent_cb);
692 dSP;
694 ENTER;
695 SAVETMPS;
697 PUSHMARK(SP);
698 PUTBACK;
700 ft_callback.ft_recv_request_cb = newSVsv(ft_recv_request_cb);
701 ft_callback.ft_recv_accept_cb = newSVsv(ft_recv_accept_cb);
702 ft_callback.ft_recv_start_cb = newSVsv(ft_recv_start_cb);
703 ft_callback.ft_recv_cancel_cb = newSVsv(ft_recv_cancel_cb);
704 ft_callback.ft_recv_complete_cb = newSVsv(ft_recv_complete_cb);
705 ft_callback.ft_send_accept_cb = newSVsv(ft_send_accept_cb);
706 ft_callback.ft_send_start_cb = newSVsv(ft_send_start_cb);
707 ft_callback.ft_send_cancel_cb = newSVsv(ft_send_cancel_cb);
708 ft_callback.ft_send_complete_cb = newSVsv(ft_send_complete_cb);
709 ft_callback.ft_read_cb = newSVsv(ft_read_cb);
710 ft_callback.ft_write_cb = newSVsv(ft_write_cb);
711 ft_callback.ft_data_not_sent_cb = newSVsv(ft_data_not_sent_cb);
713 SPAGAIN;
714 PUTBACK;
716 FREETMPS;
717 LEAVE;
723 * @brief Validate then call the given callback pointer.
724 * @param key a hash key for the local function callback table.
725 * @return boolean as int (0 || 1) on success
727 int func_call(gpointer key)
729 /* @exception key cannot be NULL */
730 g_return_val_if_fail(key != NULL,TFALSE);
732 gpointer orig_key = NULL;
733 hash_record *record = NULL;
734 int (*timeout_add_caller)(gpointer);
735 int (*input_add_caller)(gpointer, gint, PurpleInputCondition);
736 int ret = 0;
738 /* Crazy voodoo madness!
739 * We're using direct pointers as keys and a struct at the
740 * end of the pointer, so no need to get any values.
742 if (g_hash_table_lookup_extended(callback_funcs, key, &orig_key, NULL))
744 record = orig_key;
746 if (record->type == TIMEOUT_ADD)
748 timeout_add_caller = record->function;
749 ret = (timeout_add_caller)(record->data);
752 else if (record->type == INPUT_ADD)
754 input_add_caller = record->function;
755 (input_add_caller)(record->data, record->fd,
756 record->cond);
757 // these functions are void
758 ret = TRUE;
761 if (ret == FALSE)
763 /* We're never gonna be calling this again, so get rid of it */
764 g_hash_table_remove(callback_funcs, record);
767 return (ret);
770 else
772 purple_debug_info("thrasher", "Callback for [%p] does not exist!\n", key);
775 return(TFALSE);
780 * @brief Wraps Glib::Source->remove
781 * @param key to return to the Perl end
782 * @return Success of call, including the success of the callback
784 gboolean thrasher_wrapper_call_source_remove (long key)
786 long ret = FALSE;
788 /* @exception key cannot be NULL */
789 g_return_val_if_fail(callback.source_remove, 0);
791 /* @exception callback.source_remove cannot be NULL */
792 g_return_val_if_fail(key != TNULL, 0);
794 dSP;
796 ENTER;
797 SAVETMPS;
799 PUSHMARK(sp);
801 /* Push the key onto the stack */
802 XPUSHs(sv_2mortal(newSViv(key)));
804 PUTBACK;
806 call_sv(callback.source_remove, G_EVAL | G_SCALAR);
808 SPAGAIN;
810 ret = POPl;
812 FREETMPS;
813 PUTBACK;
815 LEAVE;
817 /* Remove call from lookup table */
818 g_hash_table_remove(perl_key_lookup, (gpointer)&key);
820 return ret;
825 * PurpleInputCondition is a bitwise OR of read/write events
826 * PurpleInputFunction is a callback to a guint function with the args gpointer, gint, and PurpleInoutCondition
829 * @brief Wraps Glib::IO->add_watch
830 * @param File descriptor
831 * @param Condition to trigger on
832 * @param Nested callback
833 * @param Data to pass to callback
834 * @return Hash key
836 long thrasher_wrapper_set_input_add (guint fd, PurpleInputCondition cond, PurpleInputFunction function, gpointer data)
838 g_return_val_if_fail(callback.input_add, 0);
839 g_return_val_if_fail(function != NULL, 0);
841 long key = 0;
843 hash_record *record = (hash_record *)malloc(sizeof(hash_record));
845 record->type = INPUT_ADD;
846 record->function = function;
847 record->data = data;
848 record->cond = cond;
849 record->fd = fd;
851 g_hash_table_insert(callback_funcs, record, NULL);
853 key = PTR_TO_LONG(record);
854 g_return_val_if_fail(key != 0, 0);
856 dSP;
858 ENTER;
859 SAVETMPS;
862 SV *pc = NULL;
863 CV *temp_cv = NULL;
864 SV *svkey = NULL;
867 svkey = newSViv(key);
868 temp_cv = get_cv(trigger_input_func_name, FALSE);
870 if (temp_cv)
872 pc = newRV((SV*)temp_cv);
874 /* Ok, this is hairy, first we push the vars we want to send to the
875 * timeout_add Perl callback. Next we push the var we want to give
876 * back to ourselves via trigger_input_func.
879 PUSHMARK(sp);
881 XPUSHs(sv_2mortal(newSViv(fd)));
882 XPUSHs(sv_2mortal(newSViv(cond)));
883 XPUSHs(sv_2mortal(pc));
884 XPUSHs(sv_2mortal(svkey));
886 PUTBACK;
888 call_sv(callback.input_add, G_EVAL | G_SCALAR);
889 SPAGAIN;
891 /* Tweak our internal key to an external key */
892 key = POPl;
894 else
896 croak("We've got serious problems, cannot create CV* to [%s]", trigger_input_func_name);
899 g_hash_table_insert(perl_key_lookup, (gpointer)&key, record);
901 return key;
906 * @brief Wraps Glib::Timeout->add
907 * @param interval in milliseconds
908 * @param callback
909 * @param data to pass to the callback
910 * @return Key for the internal timeout_add function hash. (Should this really be returning anything?!)
912 long thrasher_wrapper_set_timeout_add (guint interval, GSourceFunc function, gpointer data)
914 /* @exception callback.timeout_add cannot be NULL */
915 g_return_val_if_fail(callback.timeout_add, 0);
916 g_return_val_if_fail(function != NULL, TFALSE);
918 long key = 0;
920 hash_record *record = (hash_record *)malloc(sizeof(hash_record *));
922 record->type = TIMEOUT_ADD;
923 record->function = function;
924 record->data = data;
926 g_hash_table_insert(callback_funcs, record, NULL);
928 key = PTR_TO_LONG(record);
929 g_return_val_if_fail(key != 0, 0);
931 dSP;
933 ENTER;
934 SAVETMPS;
937 SV *pc = NULL;
938 CV *temp_cv = NULL;
939 SV *svkey = NULL;
941 svkey = newSViv(key);
942 temp_cv = get_cv(trigger_timeout_func_name, FALSE);
944 if (temp_cv)
946 pc = newRV((SV*)temp_cv);
948 /* Ok, this is hairy, first we push the vars we want to send to the
949 * timeout_add Perl callback. Next we push the var we want to give
950 * back to ourselves via trigger_input_func.
953 PUSHMARK(sp);
955 XPUSHs(sv_2mortal(newSViv(interval)));
956 XPUSHs(sv_2mortal(pc));
957 XPUSHs(sv_2mortal(svkey));
959 PUTBACK;
961 call_sv(callback.timeout_add, G_EVAL | G_SCALAR);
963 SPAGAIN;
965 /* Tweak our internal key to an external key */
966 key = POPl;
968 else
970 croak("We've got serious problems, cannot create CV* to [%s]", trigger_input_func_name);
973 SPAGAIN;
975 FREETMPS;
976 PUTBACK;
978 LEAVE;
980 return key;
984 * @brief Send a @p message out via callback.incoming_msg
985 * @param jid user the @p message is being sent to
986 * @param sender user name of the @p message sender
987 * @param alias alternate user name of the @p sender (may be NULL)
988 * @param message string being sent to @p jid
989 * @return 1 on success, 0 on failure
991 int thrasher_wrapper_incoming_msg (const char *jid,
992 const char *sender,
993 const char *alias,
994 const char *message,
995 PurpleMessageFlags flags)
997 int ret = 0;
999 /* @exception jid, sender, and message cannot be NULL */
1000 g_return_val_if_fail(jid != NULL, 0);
1001 g_return_val_if_fail(sender != NULL, 0);
1002 g_return_val_if_fail(message != NULL, 0);
1004 /* @exception bail if our callback doesn't exist */
1005 g_return_val_if_fail(callback.incoming_msg != NULL, 0);
1007 dSP;
1009 ENTER;
1010 SAVETMPS;
1012 PUSHMARK(sp);
1014 /* Push the args onto the stack */
1015 XPUSHs(sv_2mortal(newSVpvn(jid, strlen(jid))));
1016 XPUSHs(sv_2mortal(newSVpvn(sender, strlen(sender))));
1017 XPUSHs(sv_2mortal(newSVpvn(alias, strlen(alias))));
1018 XPUSHs(sv_2mortal(newSVpvn(message, strlen(message))));
1019 XPUSHs(sv_2mortal(newSViv(flags)));
1021 PUTBACK;
1023 call_sv(callback.incoming_msg, G_EVAL | G_SCALAR);
1025 SPAGAIN;
1027 ret = POPl;
1029 FREETMPS;
1030 PUTBACK;
1032 LEAVE;
1034 return ret;
1038 * @brief Forwards the 'connection' event, that authentication has
1039 * completed
1040 * @param jid user who has successfully connected
1042 int thrasher_wrapper_connection(const char *jid)
1044 int ret = 0;
1046 /* @exception jid, sender, and message cannot be NULL */
1047 g_return_val_if_fail(jid != NULL, 0);
1049 /* @exception bail if our callback doesn't exist */
1050 g_return_val_if_fail(callback.connection != NULL, 0);
1052 dSP;
1054 ENTER;
1055 SAVETMPS;
1057 PUSHMARK(sp);
1059 /* Push the args onto the stack */
1060 XPUSHs(sv_2mortal(newSVpvn(jid, strlen(jid))));
1062 PUTBACK;
1064 call_sv(callback.connection, G_EVAL | G_SCALAR);
1066 SPAGAIN;
1068 ret = POPl;
1070 FREETMPS;
1071 PUTBACK;
1073 LEAVE;
1075 return ret;
1078 SV *clean_newSVpv(const char *inp)
1080 if (inp)
1081 return sv_2mortal(newSVpv(inp, 0));
1082 else
1083 return sv_2mortal(&PL_sv_undef);
1088 * @brief Forward presence information via callback.presence_in
1089 * @param jid user the @p status is being sent to
1090 * @param sender user name of the @p status sender
1091 * @param alias alternate user name of the @p sender (may be NULL)
1092 * @param group which the @p sender belongs to
1093 * @param status PurpleStatusPrimitive (guint) being sent to @p jid
1094 * @param message string being sent to @p jid (may be NULL)
1095 * @return 1 on success, 0 on failure (wrapped Perl)
1097 int thrasher_wrapper_presence_in(const char *jid,
1098 const char *sender,
1099 const char *alias,
1100 const char *group,
1101 const PurpleStatusPrimitive status,
1102 const char *message)
1104 int ret = 0;
1106 /* @exception jid, sender, and group cannot be NULL */
1107 g_return_val_if_fail(jid != NULL, 0);
1108 g_return_val_if_fail(sender != NULL, 0);
1109 g_return_val_if_fail(group != NULL, 0);
1111 /* @exception bail if our callback doesn't exist */
1112 g_return_val_if_fail(callback.presence_in != NULL, 0);
1114 dSP;
1116 ENTER;
1117 SAVETMPS;
1119 PUSHMARK(sp);
1121 /* Push the args onto the stack */
1122 XPUSHs(clean_newSVpv(jid));
1123 XPUSHs(clean_newSVpv(sender));
1124 XPUSHs(clean_newSVpv(alias));
1125 XPUSHs(clean_newSVpv(group));
1126 XPUSHs(sv_2mortal(newSViv(status)));
1127 XPUSHs(clean_newSVpv(message));
1129 PUTBACK;
1131 call_sv(callback.presence_in, G_EVAL | G_SCALAR);
1133 SPAGAIN;
1135 ret = POPl;
1137 FREETMPS;
1138 PUTBACK;
1140 LEAVE;
1142 return ret;
1145 int thrasher_wrapper_legacy_user_add_user(const char *jid,
1146 const char *sender) {
1147 int ret = 0;
1149 g_return_val_if_fail(jid, 0);
1150 g_return_val_if_fail(sender, 0);
1152 g_return_val_if_fail(callback.legacy_user_add_user, 0);
1154 dSP;
1156 ENTER;
1157 SAVETMPS;
1159 PUSHMARK(sp);
1161 XPUSHs(clean_newSVpv(jid));
1162 XPUSHs(clean_newSVpv(sender));
1164 PUTBACK;
1166 call_sv(callback.legacy_user_add_user, G_EVAL | G_SCALAR);
1168 SPAGAIN;
1170 ret = POPl;
1172 FREETMPS;
1173 PUTBACK;
1175 LEAVE;
1177 return ret;
1181 * @brief Forward subscription request information via callback.subscription_add
1182 * @param jid user the @p status is being sent to
1183 * @param sender user name of the requester
1184 * @param status PurpleStatusPrimitive (guint) being sent to @p jid
1185 * @return 1 on success, 0 on failure (wrapped Perl)
1187 int thrasher_wrapper_subscription_add(const char *jid,
1188 const char *sender,
1189 guint status)
1191 int ret = 0;
1193 /* @exception jid, sender, and group cannot be NULL */
1194 g_return_val_if_fail(jid != NULL, 0);
1195 g_return_val_if_fail(sender != NULL, 0);
1197 /* @exception bail if our callback doesn't exist */
1198 g_return_val_if_fail(callback.subscription_add != NULL, 0);
1200 dSP;
1202 ENTER;
1203 SAVETMPS;
1205 PUSHMARK(sp);
1207 /* Push the args onto the stack */
1208 XPUSHs(clean_newSVpv(jid));
1209 XPUSHs(clean_newSVpv(sender));
1210 XPUSHs(sv_2mortal(newSViv(status)));
1212 PUTBACK;
1214 call_sv(callback.subscription_add, G_EVAL | G_SCALAR);
1216 SPAGAIN;
1218 ret = POPl;
1220 FREETMPS;
1221 PUTBACK;
1223 LEAVE;
1225 return ret;
1229 * @brief Forward connection error messages via callback.connection_error
1230 * @param jid user the @p error_code is being sent to
1231 * @param error_code PurpleConnectionError (guint) being sent to @p jid
1232 * @param message a error message (may be NULL)
1233 * @return 1 on success, 0 on failure (wrapped Perl)
1235 int thrasher_wrapper_connection_error(const char *jid,
1236 const guint error_code,
1237 const char *message)
1239 int ret = 0;
1241 // If the JID is null, pick it up off of
1242 // thrasher.c:current_login_jid, per the comment in that file.
1243 if (jid == NULL) {
1244 purple_debug_info("thrasher", "had to retrieve the jid from mem\n");
1245 jid = get_current_login_jid();
1248 // If we're in the middle of a connection, pick up that we have an error.
1249 set_got_error(1);
1251 g_return_val_if_fail(jid, 0);
1254 /* @exception error_code cannot be less than zero or greater than PURPLE_CONNECTION_ERROR_OTHER_ERROR */
1255 g_return_val_if_fail(error_code >= 0, ret);
1256 g_return_val_if_fail(error_code <= PURPLE_CONNECTION_ERROR_OTHER_ERROR, ret);
1258 dSP;
1260 ENTER;
1261 SAVETMPS;
1263 PUSHMARK(sp);
1265 /* Push the args onto the stack */
1266 XPUSHs(clean_newSVpv(jid));
1267 XPUSHs(sv_2mortal(newSViv(error_code)));
1268 XPUSHs(clean_newSVpv(message));
1270 PUTBACK;
1272 call_sv(callback.connection_error, G_EVAL | G_SCALAR);
1274 SPAGAIN;
1276 ret = POPl;
1278 FREETMPS;
1279 PUTBACK;
1281 LEAVE;
1283 // Remove the account from our current active set
1284 thrasher_remove_account(jid);
1286 return ret;
1289 void thrasher_remove_account (const char *jid)
1291 PurpleAccount *account;
1293 g_return_if_fail(jid != NULL);
1295 purple_debug_info("thrasher", "Removing active account for %s\n",
1296 jid);
1298 account = g_hash_table_lookup(accts, jid);
1299 if (account != NULL)
1301 g_hash_table_remove(accts, jid);
1306 * @brief Forwards typing notification state changes from libpurple buddies.
1307 * @param pa Account receiving the notification
1308 * @param who Buddy name sending the notification
1309 * @param state
1311 void thrasher_wrapper_incoming_chatstate(PurpleAccount* pa,
1312 const char* who,
1313 PurpleTypingState state) {
1314 /* @exception pa and who cannot be NULL */
1315 g_return_if_fail(pa != NULL);
1316 g_return_if_fail(who != NULL);
1318 /* bail if Perl-side doesn't implement this */
1319 if (! callback.incoming_chatstate) {
1320 return;
1323 gchar* jid = thrasher_account_get_jid(pa);
1324 /* @exception bail if account has no JID */
1325 g_return_if_fail(jid != NULL);
1327 dSP;
1329 ENTER;
1330 SAVETMPS;
1332 PUSHMARK(sp);
1334 /* Push the args onto the stack */
1335 XPUSHs(clean_newSVpv(jid));
1336 XPUSHs(clean_newSVpv(who));
1337 XPUSHs(sv_2mortal(newSViv(state)));
1339 PUTBACK;
1341 call_sv(callback.incoming_chatstate, G_EVAL | G_SCALAR);
1343 SPAGAIN;
1345 FREETMPS;
1346 PUTBACK;
1348 LEAVE;
1352 * @begin External trigger to flush the internal function tables
1354 void thrasher_wrapper_destructor ()
1356 g_hash_table_destroy(callback_funcs);
1357 g_hash_table_destroy(perl_key_lookup);
1360 size_t
1361 thrasher_wrapper_send_file(const char *jid,
1362 const char *who,
1363 char *filename,
1364 guint size,
1365 char *desc) {
1366 g_return_val_if_fail(jid, 0);
1367 g_return_val_if_fail(who, 0);
1369 purple_debug_info("thrasher ft",
1370 "Sending file %s -> %s\n",
1371 jid, who);
1373 PurpleAccount *account = g_hash_table_lookup(accts, jid);
1375 g_return_val_if_fail(account, 0);
1377 return thrasher_send_file(account, who, filename, size, desc);
1380 void
1381 thrasher_action_ft_ui_ready(size_t id) {
1382 PurpleXfer* xfer = get_xfer_by_id(id);
1383 g_return_if_fail(xfer);
1384 purple_xfer_ui_ready(xfer);
1385 /* HACK: READY_PRPL is usually not set (or was cleared) right now,
1386 * and some prpls don't appear to set it correctly on repeats.
1387 * Spam prpl_ready to force a do_transfer anyway.
1389 * Using a separate watch on the remote xfer->fd would avoid some
1390 * EAGAINs. I'd be happy to be proven wrong, but testing found no
1391 * significant benefit to Thrasher in that.
1393 purple_xfer_prpl_ready(xfer);
1397 * @brief Called on libpurple file-send-start signal
1398 * @param id of Thrasher file transfer structure.
1401 thrasher_wrapper_ft_send_start(guint id) {
1402 int ret = 0;
1404 /* @exception bail if our callback doesn't exist */
1405 g_return_val_if_fail(ft_callback.ft_send_start_cb != NULL, 0);
1407 dSP;
1409 ENTER;
1410 SAVETMPS;
1412 PUSHMARK(sp);
1414 /* Push the args onto the stack */
1415 XPUSHs(sv_2mortal(newSViv(id)));
1417 PUTBACK;
1419 call_sv(ft_callback.ft_send_start_cb, G_EVAL | G_SCALAR);
1421 SPAGAIN;
1423 ret = POPl;
1425 FREETMPS;
1426 PUTBACK;
1428 LEAVE;
1430 return ret;
1434 * @brief Called on libpurple file-send-cancel signal
1435 * @param id of Thrasher file transfer structure.
1437 void
1438 thrasher_wrapper_ft_send_cancel(guint id) {
1439 /* @exception bail if our callback doesn't exist */
1440 g_return_if_fail(ft_callback.ft_send_cancel_cb != NULL);
1442 dSP;
1444 ENTER;
1445 SAVETMPS;
1447 PUSHMARK(sp);
1449 /* Push the args onto the stack */
1450 XPUSHs(sv_2mortal(newSViv(id)));
1452 PUTBACK;
1454 call_sv(ft_callback.ft_send_cancel_cb, G_EVAL | G_SCALAR);
1456 SPAGAIN;
1458 FREETMPS;
1459 PUTBACK;
1461 LEAVE;
1463 return;
1467 * @brief Called on libpurple file-send-complete signal
1468 * @param id of Thrasher file transfer structure.
1470 void
1471 thrasher_wrapper_ft_send_complete(guint id) {
1472 /* @exception bail if our callback doesn't exist */
1473 g_return_if_fail(ft_callback.ft_send_complete_cb != NULL);
1475 dSP;
1477 ENTER;
1478 SAVETMPS;
1480 PUSHMARK(sp);
1482 /* Push the args onto the stack */
1483 XPUSHs(sv_2mortal(newSViv(id)));
1485 PUTBACK;
1487 call_sv(ft_callback.ft_send_complete_cb, G_EVAL | G_SCALAR);
1489 SPAGAIN;
1491 FREETMPS;
1492 PUTBACK;
1494 LEAVE;
1496 return;
1499 gssize
1500 thrasher_wrapper_ft_read(guint id,
1501 guchar **buffer,
1502 gssize size) {
1503 int read_sz = -1;
1504 int count = 0;
1506 /* @exception bail if our callback doesn't exist */
1507 g_return_val_if_fail(ft_callback.ft_read_cb != NULL, 0);
1509 dSP;
1511 ENTER;
1512 SAVETMPS;
1514 PUSHMARK(sp);
1516 /* Push the args onto the stack */
1517 XPUSHs(sv_2mortal(newSViv(id)));
1518 XPUSHs(sv_2mortal(newSViv(size)));
1520 PUTBACK;
1522 count = call_sv(ft_callback.ft_read_cb, G_EVAL | G_ARRAY);
1524 SPAGAIN;
1526 if (count == 2) {
1527 read_sz = POPl;
1528 void* read = POPpbytex;
1529 *buffer = malloc(read_sz);
1530 /* This buffer is free'd by libpurple. */
1531 memcpy(*buffer, read, read_sz);
1533 else {
1534 *buffer = NULL;
1535 read_sz = -1;
1538 FREETMPS;
1539 PUTBACK;
1541 LEAVE;
1543 return read_sz;
1546 void
1547 thrasher_wrapper_ft_data_not_sent(guint id,
1548 const char *buffer,
1549 gsize size) {
1550 /* @exception buffer cannot be NULL */
1551 g_return_if_fail(buffer != NULL);
1553 /* @exception bail if our callback doesn't exist */
1554 g_return_if_fail(ft_callback.ft_data_not_sent_cb != NULL);
1556 dSP;
1558 ENTER;
1559 SAVETMPS;
1561 PUSHMARK(sp);
1563 /* Push the args onto the stack */
1564 XPUSHs(sv_2mortal(newSViv(id)));
1565 /* buffer is not null-terminated. */
1566 XPUSHs(sv_2mortal(newSVpvn(buffer, size)));
1568 PUTBACK;
1570 call_sv(ft_callback.ft_data_not_sent_cb, G_EVAL | G_SCALAR);
1572 SPAGAIN;
1574 FREETMPS;
1575 PUTBACK;
1577 LEAVE;
1579 return;
1583 * @brief ask user's permission for ft and set up bytestream
1584 * @param xfer a filled-in PURPLE_XFER_RECEIVE PurpleXfer.
1585 * @param filename suggested local filename
1586 * @return nothing; use thrasher_action_ft_recv_request_respond when ready
1588 void
1589 thrasher_wrapper_ft_recv_request(PurpleXfer *xfer,
1590 const char* filename) {
1591 /* @exception xfer cannot be NULL */
1592 g_return_if_fail(xfer != NULL);
1594 /* @exception bail if our callback doesn't exist */
1595 g_return_if_fail(ft_callback.ft_recv_request_cb != NULL);
1597 dSP;
1599 ENTER;
1600 SAVETMPS;
1602 PUSHMARK(sp);
1604 /* Push the args onto the stack */
1605 guint id = get_id_by_xfer(xfer);
1606 XPUSHs(sv_2mortal(newSViv(id)));
1607 PurpleAccount* pa = purple_xfer_get_account(xfer);
1608 gchar* jid = thrasher_account_get_jid(pa);
1609 XPUSHs(clean_newSVpv(jid));
1610 const char* who = purple_xfer_get_remote_user(xfer);
1611 XPUSHs(clean_newSVpv(who));
1612 XPUSHs(clean_newSVpv(filename));
1613 size_t size = purple_xfer_get_size(xfer);
1614 XPUSHs(sv_2mortal(newSViv(size)));
1616 PUTBACK;
1618 call_sv(ft_callback.ft_recv_request_cb, G_EVAL | G_SCALAR);
1620 SPAGAIN;
1622 FREETMPS;
1623 PUTBACK;
1625 LEAVE;
1627 return;
1630 void
1631 thrasher_action_ft_recv_request_respond(size_t id,
1632 unsigned int accept) {
1633 thrasher_xfer_recv_request_responder(id, accept);
1636 gssize
1637 thrasher_wrapper_ft_write(guint id,
1638 const char *buffer,
1639 gssize size) {
1640 int written_sz = -1;
1642 /* @exception bail if our callback doesn't exist */
1643 g_return_val_if_fail(ft_callback.ft_write_cb != NULL, 0);
1645 dSP;
1647 ENTER;
1648 SAVETMPS;
1650 PUSHMARK(sp);
1652 /* Push the args onto the stack */
1653 XPUSHs(sv_2mortal(newSViv(id)));
1654 /* buffer is not null-terminated. */
1655 XPUSHs(sv_2mortal(newSVpvn(buffer, size)));
1657 PUTBACK;
1659 call_sv(ft_callback.ft_write_cb, G_EVAL | G_ARRAY);
1661 SPAGAIN;
1663 written_sz = POPl;
1665 FREETMPS;
1666 PUTBACK;
1668 LEAVE;
1670 return written_sz;
1674 * @brief Called on libpurple file-recv-cancel signal
1675 * @param id of Thrasher file transfer structure.
1677 void
1678 thrasher_wrapper_ft_recv_cancel(guint id) {
1679 /* @exception bail if our callback doesn't exist */
1680 g_return_if_fail(ft_callback.ft_recv_cancel_cb != NULL);
1682 dSP;
1684 ENTER;
1685 SAVETMPS;
1687 PUSHMARK(sp);
1689 /* Push the args onto the stack */
1690 XPUSHs(sv_2mortal(newSViv(id)));
1692 PUTBACK;
1694 call_sv(ft_callback.ft_recv_cancel_cb, G_EVAL | G_SCALAR);
1696 SPAGAIN;
1698 FREETMPS;
1699 PUTBACK;
1701 LEAVE;
1703 return;
1707 * @brief Called on libpurple file-recv-complete signal
1708 * @param id of Thrasher file transfer structure.
1710 void
1711 thrasher_wrapper_ft_recv_complete(guint id) {
1712 /* @exception bail if our callback doesn't exist */
1713 g_return_if_fail(ft_callback.ft_recv_complete_cb != NULL);
1715 dSP;
1717 ENTER;
1718 SAVETMPS;
1720 PUSHMARK(sp);
1722 /* Push the args onto the stack */
1723 XPUSHs(sv_2mortal(newSViv(id)));
1725 PUTBACK;
1727 call_sv(ft_callback.ft_recv_complete_cb, G_EVAL | G_SCALAR);
1729 SPAGAIN;
1731 FREETMPS;
1732 PUTBACK;
1734 LEAVE;
1736 return;
1739 void
1740 thrasher_action_ft_cancel_local(size_t id) {
1741 PurpleXfer* xfer = get_xfer_by_id(id);
1742 purple_xfer_cancel_local(xfer);
1745 #ifdef TH_DEBUG
1746 /*********************************************************
1748 * Only debugging stuff beyond this point!
1750 *********************************************************/
1751 gboolean foo_callback (void *data);
1752 gboolean foo_callback2 (void *data);
1753 void foo_callback3 (gpointer data, gint fd, PurpleInputCondition cond);
1754 long thrasher_wrapper_trigger_timeout_add (void);
1755 long thrasher_wrapper_trigger_timeout_add2 (void);
1756 long thrasher_wrapper_trigger_timeout_add3 (void);
1757 long thrasher_wrapper_trigger_input_add (void);
1758 void thrasher_wrapper_dump_calls (void);
1759 void spit_func_info (gpointer key, gpointer value, gpointer user_data);
1762 gboolean
1763 foo_callback (void *data)
1765 printf("!!!!!!Calling a callback w/ data [%s]!!!!\n", (char *)data);
1766 return TRUE;
1770 int cntr = 9;
1773 gboolean
1774 foo_callback2 (void *data)
1776 printf("will die in [%d] cycles\n", cntr);
1778 if (cntr--)
1779 return TRUE;
1780 else
1781 return FALSE;
1784 void foo_callback3 (gpointer data, gint fd, PurpleInputCondition cond)
1786 printf("data [%p]\tfd [%d]\tcond [%d]\n", data, fd, cond);
1789 /* Test functions to allow an external caller to trigger internal actions */
1790 long thrasher_wrapper_trigger_timeout_add ()
1792 printf("trigger_timeout_add called\n");
1793 return thrasher_wrapper_set_timeout_add(1234, foo_callback, "YAY! test data");
1797 long thrasher_wrapper_trigger_timeout_add2 ()
1799 printf("trigger_timeout_add2 called\n");
1800 return thrasher_wrapper_set_timeout_add(1867, foo_callback, "test data says what?");
1803 long thrasher_wrapper_trigger_timeout_add3 ()
1805 printf("trigger_timeout_add3 called\n");
1806 return thrasher_wrapper_set_timeout_add(200, foo_callback2, "this shoudl fail shortly");
1809 long thrasher_wrapper_trigger_input_add ()
1811 printf("trigger_input_add called\n");
1813 int fd = open("/tmp/testfoo", O_RDWR);
1814 PurpleInputCondition cond;
1815 cond = PURPLE_INPUT_WRITE;
1816 long face = 0;
1817 printf("HAMMERTIME\n");
1818 face = thrasher_wrapper_set_input_add(fd, cond, foo_callback3, "double plus win ungood");
1820 return face;
1823 int thrasher_wrapper_trigger_timeout_remove (long key)
1825 printf("trigger_timeout_remove called\n");
1826 /* We currently have no way to correlate external keys to internal keys... */
1827 return !thrasher_wrapper_call_source_remove(key);
1831 void spit_func_info (gpointer key, gpointer value, gpointer user_data)
1833 hash_record *record;
1834 printf("key [%p]\tvalue [%p]\n", key, value);
1836 record = key;
1838 if (record->type == INPUT_ADD)
1840 printf("\tINPUT_ADD\n\tfunction [%p]\n\tdata [%p]\n\tcond [%d]\n\tfd [%d]\n", record->function, record->data, record->cond, record->fd);
1843 else if (record->type == TIMEOUT_ADD)
1846 printf("\tTIMEOUT_ADD\n\tfunction [%p]\n\tdata [%p]\n", record->function, record->data);
1849 else
1851 printf("record type [%d] is unrecognized!\n", record->type);
1855 void thrasher_wrapper_dump_calls ()
1857 g_hash_table_foreach(callback_funcs, spit_func_info, NULL);
1860 #endif /* TH_DEBUG */