Don't crash if Component calls request_connect() between scheduled runs.
[thrasher.git] / thperl.c
blob0e5bceeabc2c0330138f3341d21065c73e4a954e
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;
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 /* Re-requesting authorization addresses cases in which a buddy is
177 * implicitly added somewhere else but never actually sent an
178 * authorization request. Such a state was sometimes possible with
179 * Yahoo but consistently reproducible with as follows in ICQ:
181 * 1. Register user1 and user2 with the ICQ transport, each on the
182 * other's roster.
183 * 2. Remove user1's ICQ contact from user2's roster.
184 * 3. Remove user2's ICQ contact from user1's roster.
185 * 4. Add user2's ICQ contact to user1's roster.
186 * 5. thrasher_buddy_request_authorize() receives and handles an
187 * authorization request from user1 on behalf of user2. It
188 * adds user1 to user2's buddy list.
189 * 6. user1 is now fully authorized with user2.
190 * 7. user2, however, had user1 added as an *un*authorized buddy.
192 * This also means Thrasher tries to do something sensible with
193 * XMPP client operations like "re-request authorization from
194 * contact". Otherwise, they'd always be handled as no-ops because
195 * the libpurple buddy is already present.
197 gboolean need_auth;
198 if (buddy) {
199 /* Re-add: check if (re-)auth is needed. */
200 need_auth = ! thrasher_account_buddy_is_authorized(account, buddy);
202 else {
203 /* Not yet on list: assume an auth request is needed. */
204 need_auth = 1;
206 /* FIXME: There's no cross-prpl calls for checking/sending
207 * authorization like oscar.c:purple_auth_request()? Could try to
208 * parse for the menu items added by each prpl we know requires
209 * authorization. :(
211 if (need_auth) {
212 purple_debug_info("thrasher",
213 "%s needs authorization from %s\n",
214 jid, buddy_name);
217 // FIXME: Figure out if we need this buddy list stuff
218 // at all. We don't actually care, as long as we get
219 // the messages and presence to send out.
221 /* We're done if buddy already exists and is authorized.
223 * libpurple says prpl should interpret a re-add of a existent
224 * buddy as an attempt to (re)send an authorization request if
225 * applicable.
227 if (buddy == NULL || need_auth)
229 char *default_group = "General";
231 /* Check to see if default group exists */
232 group = purple_find_group(default_group);
234 /* If not, create default group */
235 if (group == NULL)
236 group = purple_group_new(default_group);
238 purple_blist_add_group(group, NULL);
240 /* Add buddy to default group (NULL should be alias...) */
241 buddy = purple_buddy_new(account, buddy_name, NULL);
242 purple_blist_add_buddy(buddy, NULL, group, NULL);
243 purple_account_add_buddy(account, buddy);
245 purple_debug_info("thrasher",
246 "finished adding %s to %s's buddy list\n",
247 buddy_name, jid);
249 return 0;
250 } else {
251 purple_debug_info("thrasher",
252 "%s already on %s's buddy list\n",
253 buddy_name, jid);
256 /* Buddy already exists */
257 return 1;
261 * @brief remove buddy (@p buddy_name) from given @p jid buddy list
262 * @param jid releational key to bound to account
263 * @param buddy_name name of buddy
264 * @return 1 if there is a problem, 0 otherwise (C)
266 int thrasher_action_buddy_remove (char *jid, char *buddy_name)
268 PurpleGroup *group = NULL;
269 PurpleBuddy *buddy = NULL;
270 PurpleAccount *account = NULL;
272 purple_debug_info("thrasher", "Buddy remove\n");
274 account = g_hash_table_lookup(accts, jid);
275 g_return_val_if_fail(account != NULL, 1);
277 // Don't try and remove a buddy if the account
278 // isn't connected
279 if (purple_account_is_connected(account)) {
281 purple_debug_info("thrasher", "find_buddy...\n");
282 buddy = purple_find_buddy(account, buddy_name);
284 /* Only remove a buddy if they exist */
285 if (buddy != NULL)
287 purple_debug_info("thrasher", "get_group...\n");
288 /* Give up us the group */
289 group = purple_buddy_get_group(buddy);
291 purple_debug_info("thrasher", "account_remove_buddy...\n");
292 /* Remove server-side buddy entry */
293 purple_account_remove_buddy(account, buddy, group);
295 purple_debug_info("thrasher", "blist_remove_buddy...\n");
296 /* Also need to remove buddy entry from memory */
297 purple_blist_remove_buddy(buddy);
299 return 0;
303 /* Account isn't connected or buddy is not found */
304 return 1;
307 void thrasher_action_buddy_authorize(char *jid, char *legacy_username)
309 thrasher_action_buddy_add(jid, legacy_username);
312 void thrasher_action_buddy_deauthorize(char *jid, char *legacy_username) {
313 thrasher_action_buddy_remove(jid, legacy_username);
317 * @brief update presence status for given @p jid
318 * @param jid releational key to bound to account
319 * @param status integer representation of PurpleStatusPrimitive
320 * @param message a status message (may be NULL)
321 * @return 1 if there is a problem, 0 otherwise (C)
323 int thrasher_action_presence (char *jid, int status, char *message)
325 PurpleAccount *account;
327 g_return_val_if_fail(jid != NULL, 0);
329 account = g_hash_table_lookup(accts, jid);
331 if (account == NULL)
332 return 1;
334 thrasher_set_presence(account, status, message);
336 return 0;
341 * @brief Send @p message from the user given by @p jid to the user @p recipient
342 * @param jid
343 * @param recipient
344 * @param message
345 * @return 1 on success 0 on failure
347 int thrasher_action_outgoing_msg (char *jid, char *recipient, char *message)
349 int ret = 0;
350 PurpleAccount *account = NULL;
352 g_return_val_if_fail(jid != NULL, 0);
353 g_return_val_if_fail(recipient != NULL, 0);
354 g_return_val_if_fail(message != NULL, 0);
356 purple_debug_info("thrasher", "Send message request\n");
357 /* Pull in user if they exist */
358 account = g_hash_table_lookup(accts, jid);
360 /* User does not exist (may not be logged in) */
361 if (account == NULL) {
362 return 0;
365 /* Try to send message (message might not be sent) */
366 if (thrasher_send_msg(account,recipient,message) == TRUE)
367 ret = 1;
369 return ret;
374 * @brief Send @p chatstate from the user given by @p jid to the @p recipient
375 * @param jid
376 * @param recipient
377 * @param chatstate
379 void
380 thrasher_action_outgoing_chatstate(char *jid,
381 char *recipient,
382 PurpleTypingState chatstate) {
383 g_return_if_fail(jid != NULL);
384 g_return_if_fail(recipient != NULL);
386 PurpleAccount *pa = NULL;
387 pa = g_hash_table_lookup(accts, jid);
388 if (pa == NULL) {
389 /* User does not exist (may not be logged in) */
390 return;
393 /* Try to send chatstate */
394 thrasher_outgoing_chatstate(pa, recipient, chatstate);
399 * @brief Log the user out with the given jid
400 * @param jid releational key to bind to account
401 * @return 1 if logout was successful, 0 if it was not
403 * NOTE: As we're going to be running a process for each protocol,
404 * there's no need to worry about the protocol used.
406 int thrasher_action_logout (char *jid)
408 int ret = 0;
409 PurpleAccount *account;
411 g_return_val_if_fail(jid != NULL, 0);
413 purple_debug_info("thrasher", "Logout requested for %s\n", jid);
415 /* Verify user is logged in */
416 account =g_hash_table_lookup(accts, jid);
417 if (account != NULL)
419 thrasher_logout(account);
420 thrasher_remove_account(jid);
421 ret = 1;
424 return ret;
428 void _thrasher_action_debug_logged_in_accts_printer(gpointer key, gpointer value, gpointer user_data);
431 * @brief Dump active account name lists to STDERR from libpurple and thperl.
433 void
434 thrasher_action_debug_logged_in() {
435 fprintf(stderr, "purple_accounts_get_all_active:\n");
436 GList* list = purple_accounts_get_all_active();
437 if (list) {
438 GList *l = list;
439 while (l) {
440 PurpleAccount *pa = (PurpleAccount*)l->data;
441 fprintf(stderr, "\t%s = %p\n", purple_account_get_username(pa), pa);
442 l = l->next;
444 g_list_free(list);
447 if (accts) {
448 fprintf(stderr, "thperl accts:\n");
449 g_hash_table_foreach(accts,
450 _thrasher_action_debug_logged_in_accts_printer,
451 NULL);
453 else {
454 fprintf(stderr, "No thperl accts?!!\n");
458 void
459 _thrasher_action_debug_logged_in_accts_printer(gpointer key,
460 gpointer value,
461 gpointer user_data) {
462 const char *jid = (char*)key;
463 PurpleAccount *pa = (PurpleAccount*)value;
464 fprintf(stderr, "\t%s = %s = %p\n",
465 jid, purple_account_get_username(pa), pa);
470 * @brief Destruction function for our internal function hash
471 * @param key data element
473 void destroy_record_key (gpointer key)
475 free(key);
480 * @brief Bizarro passthrough to allow perl calling of dynamically-set C events
481 * @param key a guint value corresponding to the pointer of a callback.
482 * @return boolean as int (0 || 1) on success. Returns 0 on failure as well.
484 int thrasher_wrapper_trigger_timeout_func(long key)
486 /* @exception key cannot be NULL */
487 g_return_val_if_fail(key != TNULL,TFALSE);
489 char *real_key = LONG_TO_PTR(key);
491 return func_call(real_key);
495 * @brief Bizarro passthrough (two) to allow perl calling of dynamically-set C events
496 * @param file descriptor
497 * @param condition trigger was called on
498 * @param key a long value corresponding to the pointer of a callback.
499 * @return boolean as int (0 || 1) on success. Returns 0 on failure as well.
501 int thrasher_wrapper_trigger_input_func(long fd, SV *cond, long key)
503 /* @exception fd cannot be zero */
504 g_return_val_if_fail(fd != 0, 0);
505 /* @exception cond cannot be zero */
506 g_return_val_if_fail(cond != 0, 0);
507 /* @exception key cannot be zero */
508 g_return_val_if_fail(key != 0, 0);
510 // GIOCondition real_cond = cond;
511 char *real_key = LONG_TO_PTR(key);
513 /* FIXME: We need to pass cond and key to this call... */
514 return func_call(real_key);
518 * @brief Simple verifcation for callbacks
519 * @param subref to callback
522 void validate_callback (SV *cb)
524 if (!SvROK(cb) || SvTYPE(SvRV(cb)) != SVt_PVCV)
526 croak("Not a callback!");
527 exit(0);
533 * @brief Initialize our wrapper
534 * @param subref (SV *) to call for timeout_add
535 * @param subref (SV *) to call for input_add
536 * @param subref (SV *) to call for source_remove
537 * @param subref (SV *) to call on incoming messages
538 * @param subref (SV *) to call on incoming presence information
539 * @param subref (SV *) to call on subscription requests
540 * @param subref (SV *) to call on subscription requests from legacy users
541 * @param subref (SV *) to call on connection errors
542 * @param subref (SV *) to call when connections have succeeded
543 * @param subref (SV *) to call on incoming chatstate (optional)
545 void thrasher_wrapper_init (SV *timeout_add_cb,
546 SV *input_add_cb,
547 SV *source_remove_cb,
548 SV *incoming_msg_cb,
549 SV *presence_in_cb,
550 SV *subscription_add_cb,
551 SV *legacy_user_add_user_cb,
552 SV *connection_error_cb,
553 SV *connection_cb,
554 ...)
556 va_list argp;
557 va_start(argp, connection_cb);
558 SV* incoming_chatstate_cb = va_arg(argp, SV*);
560 g_return_if_fail(timeout_add_cb);
561 g_return_if_fail(input_add_cb);
562 g_return_if_fail(source_remove_cb);
563 g_return_if_fail(incoming_msg_cb);
564 g_return_if_fail(presence_in_cb);
565 g_return_if_fail(subscription_add_cb);
566 g_return_if_fail(legacy_user_add_user_cb);
567 g_return_if_fail(connection_error_cb);
568 g_return_if_fail(connection_cb);
570 /* Set the bindings for our function table */
571 callback_funcs = g_hash_table_new_full(g_direct_hash,
572 g_direct_equal,
573 destroy_record_key,
574 NULL);
576 /* Create the lookup table so we know what to do when Perl triggers a remove */
577 perl_key_lookup = g_hash_table_new(g_int_hash, g_int_equal);
579 /* Validate callback, this burns up if it fails so no
580 * need to check for errors!
582 validate_callback(timeout_add_cb);
583 validate_callback(input_add_cb);
584 validate_callback(source_remove_cb);
585 validate_callback(incoming_msg_cb);
586 validate_callback(presence_in_cb);
587 validate_callback(subscription_add_cb);
588 validate_callback(legacy_user_add_user_cb);
589 validate_callback(connection_error_cb);
590 validate_callback(connection_cb);
591 if (incoming_chatstate_cb) {
592 validate_callback(incoming_chatstate_cb);
595 dSP;
597 ENTER;
598 SAVETMPS;
600 PUSHMARK(SP);
601 PUTBACK;
603 callback.timeout_add = newSVsv(timeout_add_cb);
604 callback.input_add = newSVsv(input_add_cb);
605 callback.source_remove = newSVsv(source_remove_cb);
606 callback.incoming_msg = newSVsv(incoming_msg_cb);
607 callback.presence_in = newSVsv(presence_in_cb);
608 callback.subscription_add = newSVsv(subscription_add_cb);
609 callback.legacy_user_add_user = newSVsv(legacy_user_add_user_cb);
610 callback.connection_error = newSVsv(connection_error_cb);
611 callback.connection = newSVsv(connection_cb);
612 if (incoming_chatstate_cb) {
613 callback.incoming_chatstate = newSVsv(incoming_chatstate_cb);
615 else {
616 callback.incoming_chatstate = 0;
619 SPAGAIN;
620 PUTBACK;
622 FREETMPS;
623 LEAVE;
627 * @brief Initialize our file transfer wrapper
628 * @param subref (SV *) to call for recv_accept
629 * @param subref (SV *) to call for recv_start
630 * @param subref (SV *) to call for recv_cancel
631 * @param subref (SV *) to call for recv_complete
632 * @param subref (SV *) to call for send_accept
633 * @param subref (SV *) to call for send_start
634 * @param subref (SV *) to call for send_cancel
635 * @param subref (SV *) to call for send_complete
636 * @param subref (SV *) to call for ui_read. Must return (buffer, size).
637 * @param subref (SV *) to call for ui_write
638 * @param subref (SV *) to call for ui_data_not_sent
640 void thrasher_wrapper_ft_init(SV *ft_recv_request_cb,
641 SV *ft_recv_accept_cb,
642 SV *ft_recv_start_cb,
643 SV *ft_recv_cancel_cb,
644 SV *ft_recv_complete_cb,
645 SV *ft_send_accept_cb,
646 SV *ft_send_start_cb,
647 SV *ft_send_cancel_cb,
648 SV *ft_send_complete_cb,
649 SV *ft_read_cb,
650 SV *ft_write_cb,
651 SV *ft_data_not_sent_cb)
653 g_return_if_fail(ft_recv_request_cb);
654 g_return_if_fail(ft_recv_accept_cb);
655 g_return_if_fail(ft_recv_start_cb);
656 g_return_if_fail(ft_recv_cancel_cb);
657 g_return_if_fail(ft_recv_complete_cb);
658 g_return_if_fail(ft_send_accept_cb);
659 g_return_if_fail(ft_send_start_cb);
660 g_return_if_fail(ft_send_cancel_cb);
661 g_return_if_fail(ft_send_complete_cb);
662 g_return_if_fail(ft_read_cb);
663 g_return_if_fail(ft_write_cb);
664 g_return_if_fail(ft_data_not_sent_cb);
666 // This assumes thrasher_wrapper_init was already called.
667 g_return_if_fail(callback_funcs);
669 validate_callback(ft_recv_request_cb);
670 validate_callback(ft_recv_accept_cb);
671 validate_callback(ft_recv_start_cb);
672 validate_callback(ft_recv_cancel_cb);
673 validate_callback(ft_recv_complete_cb);
674 validate_callback(ft_send_accept_cb);
675 validate_callback(ft_send_start_cb);
676 validate_callback(ft_send_cancel_cb);
677 validate_callback(ft_send_complete_cb);
678 validate_callback(ft_read_cb);
679 validate_callback(ft_write_cb);
680 validate_callback(ft_data_not_sent_cb);
682 dSP;
684 ENTER;
685 SAVETMPS;
687 PUSHMARK(SP);
688 PUTBACK;
690 ft_callback.ft_recv_request_cb = newSVsv(ft_recv_request_cb);
691 ft_callback.ft_recv_accept_cb = newSVsv(ft_recv_accept_cb);
692 ft_callback.ft_recv_start_cb = newSVsv(ft_recv_start_cb);
693 ft_callback.ft_recv_cancel_cb = newSVsv(ft_recv_cancel_cb);
694 ft_callback.ft_recv_complete_cb = newSVsv(ft_recv_complete_cb);
695 ft_callback.ft_send_accept_cb = newSVsv(ft_send_accept_cb);
696 ft_callback.ft_send_start_cb = newSVsv(ft_send_start_cb);
697 ft_callback.ft_send_cancel_cb = newSVsv(ft_send_cancel_cb);
698 ft_callback.ft_send_complete_cb = newSVsv(ft_send_complete_cb);
699 ft_callback.ft_read_cb = newSVsv(ft_read_cb);
700 ft_callback.ft_write_cb = newSVsv(ft_write_cb);
701 ft_callback.ft_data_not_sent_cb = newSVsv(ft_data_not_sent_cb);
703 SPAGAIN;
704 PUTBACK;
706 FREETMPS;
707 LEAVE;
713 * @brief Validate then call the given callback pointer.
714 * @param key a hash key for the local function callback table.
715 * @return boolean as int (0 || 1) on success
717 int func_call(gpointer key)
719 /* @exception key cannot be NULL */
720 g_return_val_if_fail(key != NULL,TFALSE);
722 gpointer orig_key = NULL;
723 hash_record *record = NULL;
724 int (*timeout_add_caller)(gpointer);
725 int (*input_add_caller)(gpointer, gint, PurpleInputCondition);
726 int ret = 0;
728 /* Crazy voodoo madness!
729 * We're using direct pointers as keys and a struct at the
730 * end of the pointer, so no need to get any values.
732 if (g_hash_table_lookup_extended(callback_funcs, key, &orig_key, NULL))
734 record = orig_key;
736 if (record->type == TIMEOUT_ADD)
738 timeout_add_caller = record->function;
739 ret = (timeout_add_caller)(record->data);
742 else if (record->type == INPUT_ADD)
744 input_add_caller = record->function;
745 (input_add_caller)(record->data, record->fd,
746 record->cond);
747 // these functions are void
748 ret = TRUE;
751 if (ret == FALSE)
753 /* We're never gonna be calling this again, so get rid of it */
754 g_hash_table_remove(callback_funcs, record);
757 return (ret);
760 else
762 purple_debug_info("thrasher", "Callback for [%p] does not exist!\n", key);
765 return(TFALSE);
770 * @brief Wraps Glib::Source->remove
771 * @param key to return to the Perl end
772 * @return Success of call, including the success of the callback
774 gboolean thrasher_wrapper_call_source_remove (long key)
776 long ret = FALSE;
778 /* @exception key cannot be NULL */
779 g_return_val_if_fail(callback.source_remove, 0);
781 /* @exception callback.source_remove cannot be NULL */
782 g_return_val_if_fail(key != TNULL, 0);
784 dSP;
786 ENTER;
787 SAVETMPS;
789 PUSHMARK(sp);
791 /* Push the key onto the stack */
792 XPUSHs(sv_2mortal(newSViv(key)));
794 PUTBACK;
796 call_sv(callback.source_remove, G_EVAL | G_SCALAR);
798 SPAGAIN;
800 ret = POPl;
802 FREETMPS;
803 PUTBACK;
805 LEAVE;
807 /* Remove call from lookup table */
808 g_hash_table_remove(perl_key_lookup, (gpointer)&key);
810 return ret;
815 * PurpleInputCondition is a bitwise OR of read/write events
816 * PurpleInputFunction is a callback to a guint function with the args gpointer, gint, and PurpleInoutCondition
819 * @brief Wraps Glib::IO->add_watch
820 * @param File descriptor
821 * @param Condition to trigger on
822 * @param Nested callback
823 * @param Data to pass to callback
824 * @return Hash key
826 long thrasher_wrapper_set_input_add (guint fd, PurpleInputCondition cond, PurpleInputFunction function, gpointer data)
828 g_return_val_if_fail(callback.input_add, 0);
829 g_return_val_if_fail(function != NULL, 0);
831 long key = 0;
833 hash_record *record = (hash_record *)malloc(sizeof(hash_record));
835 record->type = INPUT_ADD;
836 record->function = function;
837 record->data = data;
838 record->cond = cond;
839 record->fd = fd;
841 g_hash_table_insert(callback_funcs, record, NULL);
843 key = PTR_TO_LONG(record);
844 g_return_val_if_fail(key != 0, 0);
846 dSP;
848 ENTER;
849 SAVETMPS;
852 SV *pc = NULL;
853 CV *temp_cv = NULL;
854 SV *svkey = NULL;
857 svkey = newSViv(key);
858 temp_cv = get_cv(trigger_input_func_name, FALSE);
860 if (temp_cv)
862 pc = newRV((SV*)temp_cv);
864 /* Ok, this is hairy, first we push the vars we want to send to the
865 * timeout_add Perl callback. Next we push the var we want to give
866 * back to ourselves via trigger_input_func.
869 PUSHMARK(sp);
871 XPUSHs(sv_2mortal(newSViv(fd)));
872 XPUSHs(sv_2mortal(newSViv(cond)));
873 XPUSHs(sv_2mortal(pc));
874 XPUSHs(sv_2mortal(svkey));
876 PUTBACK;
878 call_sv(callback.input_add, G_EVAL | G_SCALAR);
879 SPAGAIN;
881 /* Tweak our internal key to an external key */
882 key = POPl;
884 else
886 croak("We've got serious problems, cannot create CV* to [%s]", trigger_input_func_name);
889 g_hash_table_insert(perl_key_lookup, (gpointer)&key, record);
891 return key;
896 * @brief Wraps Glib::Timeout->add
897 * @param interval in milliseconds
898 * @param callback
899 * @param data to pass to the callback
900 * @return Key for the internal timeout_add function hash. (Should this really be returning anything?!)
902 long thrasher_wrapper_set_timeout_add (guint interval, GSourceFunc function, gpointer data)
904 /* @exception callback.timeout_add cannot be NULL */
905 g_return_val_if_fail(callback.timeout_add, 0);
906 g_return_val_if_fail(function != NULL, TFALSE);
908 long key = 0;
910 hash_record *record = (hash_record *)malloc(sizeof(hash_record *));
912 record->type = TIMEOUT_ADD;
913 record->function = function;
914 record->data = data;
916 g_hash_table_insert(callback_funcs, record, NULL);
918 key = PTR_TO_LONG(record);
919 g_return_val_if_fail(key != 0, 0);
921 dSP;
923 ENTER;
924 SAVETMPS;
927 SV *pc = NULL;
928 CV *temp_cv = NULL;
929 SV *svkey = NULL;
931 svkey = newSViv(key);
932 temp_cv = get_cv(trigger_timeout_func_name, FALSE);
934 if (temp_cv)
936 pc = newRV((SV*)temp_cv);
938 /* Ok, this is hairy, first we push the vars we want to send to the
939 * timeout_add Perl callback. Next we push the var we want to give
940 * back to ourselves via trigger_input_func.
943 PUSHMARK(sp);
945 XPUSHs(sv_2mortal(newSViv(interval)));
946 XPUSHs(sv_2mortal(pc));
947 XPUSHs(sv_2mortal(svkey));
949 PUTBACK;
951 call_sv(callback.timeout_add, G_EVAL | G_SCALAR);
953 SPAGAIN;
955 /* Tweak our internal key to an external key */
956 key = POPl;
958 else
960 croak("We've got serious problems, cannot create CV* to [%s]", trigger_input_func_name);
963 SPAGAIN;
965 FREETMPS;
966 PUTBACK;
968 LEAVE;
970 return key;
974 * @brief Send a @p message out via callback.incoming_msg
975 * @param jid user the @p message is being sent to
976 * @param sender user name of the @p message sender
977 * @param alias alternate user name of the @p sender (may be NULL)
978 * @param message string being sent to @p jid
979 * @return 1 on success, 0 on failure
981 int thrasher_wrapper_incoming_msg (const char *jid,
982 const char *sender,
983 const char *alias,
984 const char *message,
985 PurpleMessageFlags flags)
987 int ret = 0;
989 /* @exception jid, sender, and message cannot be NULL */
990 g_return_val_if_fail(jid != NULL, 0);
991 g_return_val_if_fail(sender != NULL, 0);
992 g_return_val_if_fail(message != NULL, 0);
994 /* @exception bail if our callback doesn't exist */
995 g_return_val_if_fail(callback.incoming_msg != NULL, 0);
997 dSP;
999 ENTER;
1000 SAVETMPS;
1002 PUSHMARK(sp);
1004 /* Push the args onto the stack */
1005 XPUSHs(sv_2mortal(newSVpvn(jid, strlen(jid))));
1006 XPUSHs(sv_2mortal(newSVpvn(sender, strlen(sender))));
1007 XPUSHs(sv_2mortal(newSVpvn(alias, strlen(alias))));
1008 XPUSHs(sv_2mortal(newSVpvn(message, strlen(message))));
1009 XPUSHs(sv_2mortal(newSViv(flags)));
1011 PUTBACK;
1013 call_sv(callback.incoming_msg, G_EVAL | G_SCALAR);
1015 SPAGAIN;
1017 ret = POPl;
1019 FREETMPS;
1020 PUTBACK;
1022 LEAVE;
1024 return ret;
1028 * @brief Forwards the 'connection' event, that authentication has
1029 * completed
1030 * @param jid user who has successfully connected
1032 int thrasher_wrapper_connection(const char *jid)
1034 int ret = 0;
1036 /* @exception jid, sender, and message cannot be NULL */
1037 g_return_val_if_fail(jid != NULL, 0);
1039 /* @exception bail if our callback doesn't exist */
1040 g_return_val_if_fail(callback.connection != NULL, 0);
1042 dSP;
1044 ENTER;
1045 SAVETMPS;
1047 PUSHMARK(sp);
1049 /* Push the args onto the stack */
1050 XPUSHs(sv_2mortal(newSVpvn(jid, strlen(jid))));
1052 PUTBACK;
1054 call_sv(callback.connection, G_EVAL | G_SCALAR);
1056 SPAGAIN;
1058 ret = POPl;
1060 FREETMPS;
1061 PUTBACK;
1063 LEAVE;
1065 return ret;
1068 SV *clean_newSVpv(const char *inp)
1070 if (inp)
1071 return sv_2mortal(newSVpv(inp, 0));
1072 else
1073 return sv_2mortal(&PL_sv_undef);
1078 * @brief Forward presence information via callback.presence_in
1079 * @param jid user the @p status is being sent to
1080 * @param sender user name of the @p status sender
1081 * @param alias alternate user name of the @p sender (may be NULL)
1082 * @param group which the @p sender belongs to
1083 * @param status PurpleStatusPrimitive (guint) being sent to @p jid
1084 * @param message string being sent to @p jid (may be NULL)
1085 * @return 1 on success, 0 on failure (wrapped Perl)
1087 int thrasher_wrapper_presence_in(const char *jid,
1088 const char *sender,
1089 const char *alias,
1090 const char *group,
1091 const PurpleStatusPrimitive status,
1092 const char *message)
1094 int ret = 0;
1096 /* @exception jid, sender, and group cannot be NULL */
1097 g_return_val_if_fail(jid != NULL, 0);
1098 g_return_val_if_fail(sender != NULL, 0);
1099 g_return_val_if_fail(group != NULL, 0);
1101 /* @exception bail if our callback doesn't exist */
1102 g_return_val_if_fail(callback.presence_in != NULL, 0);
1104 dSP;
1106 ENTER;
1107 SAVETMPS;
1109 PUSHMARK(sp);
1111 /* Push the args onto the stack */
1112 XPUSHs(clean_newSVpv(jid));
1113 XPUSHs(clean_newSVpv(sender));
1114 XPUSHs(clean_newSVpv(alias));
1115 XPUSHs(clean_newSVpv(group));
1116 XPUSHs(sv_2mortal(newSViv(status)));
1117 XPUSHs(clean_newSVpv(message));
1119 PUTBACK;
1121 call_sv(callback.presence_in, G_EVAL | G_SCALAR);
1123 SPAGAIN;
1125 ret = POPl;
1127 FREETMPS;
1128 PUTBACK;
1130 LEAVE;
1132 return ret;
1135 int thrasher_wrapper_legacy_user_add_user(const char *jid,
1136 const char *sender) {
1137 int ret = 0;
1139 g_return_val_if_fail(jid, 0);
1140 g_return_val_if_fail(sender, 0);
1142 g_return_val_if_fail(callback.legacy_user_add_user, 0);
1144 dSP;
1146 ENTER;
1147 SAVETMPS;
1149 PUSHMARK(sp);
1151 XPUSHs(clean_newSVpv(jid));
1152 XPUSHs(clean_newSVpv(sender));
1154 PUTBACK;
1156 call_sv(callback.legacy_user_add_user, G_EVAL | G_SCALAR);
1158 SPAGAIN;
1160 ret = POPl;
1162 FREETMPS;
1163 PUTBACK;
1165 LEAVE;
1167 return ret;
1171 * @brief Forward subscription request information via callback.subscription_add
1172 * @param jid user the @p status is being sent to
1173 * @param sender user name of the requester
1174 * @param status PurpleStatusPrimitive (guint) being sent to @p jid
1175 * @return 1 on success, 0 on failure (wrapped Perl)
1177 int thrasher_wrapper_subscription_add(const char *jid,
1178 const char *sender,
1179 guint status)
1181 int ret = 0;
1183 /* @exception jid, sender, and group cannot be NULL */
1184 g_return_val_if_fail(jid != NULL, 0);
1185 g_return_val_if_fail(sender != NULL, 0);
1187 /* @exception bail if our callback doesn't exist */
1188 g_return_val_if_fail(callback.subscription_add != NULL, 0);
1190 dSP;
1192 ENTER;
1193 SAVETMPS;
1195 PUSHMARK(sp);
1197 /* Push the args onto the stack */
1198 XPUSHs(clean_newSVpv(jid));
1199 XPUSHs(clean_newSVpv(sender));
1200 XPUSHs(sv_2mortal(newSViv(status)));
1202 PUTBACK;
1204 call_sv(callback.subscription_add, G_EVAL | G_SCALAR);
1206 SPAGAIN;
1208 ret = POPl;
1210 FREETMPS;
1211 PUTBACK;
1213 LEAVE;
1215 return ret;
1219 * @brief Forward connection error messages via callback.connection_error
1220 * @param jid user the @p error_code is being sent to
1221 * @param error_code PurpleConnectionError (guint) being sent to @p jid
1222 * @param message a error message (may be NULL)
1223 * @return 1 on success, 0 on failure (wrapped Perl)
1225 int thrasher_wrapper_connection_error(const char *jid,
1226 const guint error_code,
1227 const char *message)
1229 int ret = 0;
1231 // If the JID is null, pick it up off of
1232 // thrasher.c:current_login_jid, per the comment in that file.
1233 if (jid == NULL) {
1234 purple_debug_info("thrasher", "had to retrieve the jid from mem\n");
1235 jid = get_current_login_jid();
1238 // If we're in the middle of a connection, pick up that we have an error.
1239 set_got_error(1);
1241 g_return_val_if_fail(jid, 0);
1244 /* @exception error_code cannot be less than zero or greater than PURPLE_CONNECTION_ERROR_OTHER_ERROR */
1245 g_return_val_if_fail(error_code >= 0, ret);
1246 g_return_val_if_fail(error_code <= PURPLE_CONNECTION_ERROR_OTHER_ERROR, ret);
1248 dSP;
1250 ENTER;
1251 SAVETMPS;
1253 PUSHMARK(sp);
1255 /* Push the args onto the stack */
1256 XPUSHs(clean_newSVpv(jid));
1257 XPUSHs(sv_2mortal(newSViv(error_code)));
1258 XPUSHs(clean_newSVpv(message));
1260 PUTBACK;
1262 call_sv(callback.connection_error, G_EVAL | G_SCALAR);
1264 SPAGAIN;
1266 ret = POPl;
1268 FREETMPS;
1269 PUTBACK;
1271 LEAVE;
1273 // Remove the account from our current active set
1274 thrasher_remove_account(jid);
1276 return ret;
1279 void thrasher_remove_account (const char *jid)
1281 PurpleAccount *account;
1283 g_return_if_fail(jid != NULL);
1285 purple_debug_info("thrasher", "Removing active account for %s\n",
1286 jid);
1288 account = g_hash_table_lookup(accts, jid);
1289 if (account != NULL)
1291 g_hash_table_remove(accts, jid);
1296 * @brief Forwards typing notification state changes from libpurple buddies.
1297 * @param pa Account receiving the notification
1298 * @param who Buddy name sending the notification
1299 * @param state
1301 void thrasher_wrapper_incoming_chatstate(PurpleAccount* pa,
1302 const char* who,
1303 PurpleTypingState state) {
1304 /* @exception pa and who cannot be NULL */
1305 g_return_if_fail(pa != NULL);
1306 g_return_if_fail(who != NULL);
1308 /* bail if Perl-side doesn't implement this */
1309 if (! callback.incoming_chatstate) {
1310 return;
1313 gchar* jid = thrasher_account_get_jid(pa);
1314 /* @exception bail if account has no JID */
1315 g_return_if_fail(jid != NULL);
1317 dSP;
1319 ENTER;
1320 SAVETMPS;
1322 PUSHMARK(sp);
1324 /* Push the args onto the stack */
1325 XPUSHs(clean_newSVpv(jid));
1326 XPUSHs(clean_newSVpv(who));
1327 XPUSHs(sv_2mortal(newSViv(state)));
1329 PUTBACK;
1331 call_sv(callback.incoming_chatstate, G_EVAL | G_SCALAR);
1333 SPAGAIN;
1335 FREETMPS;
1336 PUTBACK;
1338 LEAVE;
1342 * @begin External trigger to flush the internal function tables
1344 void thrasher_wrapper_destructor ()
1346 g_hash_table_destroy(callback_funcs);
1347 g_hash_table_destroy(perl_key_lookup);
1350 size_t
1351 thrasher_wrapper_send_file(const char *jid,
1352 const char *who,
1353 char *filename,
1354 guint size,
1355 char *desc) {
1356 g_return_val_if_fail(jid, 0);
1357 g_return_val_if_fail(who, 0);
1359 purple_debug_info("thrasher ft",
1360 "Sending file %s -> %s\n",
1361 jid, who);
1363 PurpleAccount *account = g_hash_table_lookup(accts, jid);
1365 g_return_val_if_fail(account, 0);
1367 return thrasher_send_file(account, who, filename, size, desc);
1370 void
1371 thrasher_action_ft_ui_ready(size_t id) {
1372 PurpleXfer* xfer = get_xfer_by_id(id);
1373 g_return_if_fail(xfer);
1374 purple_xfer_ui_ready(xfer);
1375 /* HACK: READY_PRPL is usually not set (or was cleared) right now,
1376 * and some prpls don't appear to set it correctly on repeats.
1377 * Spam prpl_ready to force a do_transfer anyway.
1379 * Using a separate watch on the remote xfer->fd would avoid some
1380 * EAGAINs. I'd be happy to be proven wrong, but testing found no
1381 * significant benefit to Thrasher in that.
1383 purple_xfer_prpl_ready(xfer);
1387 * @brief Called on libpurple file-send-start signal
1388 * @param id of Thrasher file transfer structure.
1391 thrasher_wrapper_ft_send_start(guint id) {
1392 int ret = 0;
1394 /* @exception bail if our callback doesn't exist */
1395 g_return_val_if_fail(ft_callback.ft_send_start_cb != NULL, 0);
1397 dSP;
1399 ENTER;
1400 SAVETMPS;
1402 PUSHMARK(sp);
1404 /* Push the args onto the stack */
1405 XPUSHs(sv_2mortal(newSViv(id)));
1407 PUTBACK;
1409 call_sv(ft_callback.ft_send_start_cb, G_EVAL | G_SCALAR);
1411 SPAGAIN;
1413 ret = POPl;
1415 FREETMPS;
1416 PUTBACK;
1418 LEAVE;
1420 return ret;
1424 * @brief Called on libpurple file-send-cancel signal
1425 * @param id of Thrasher file transfer structure.
1427 void
1428 thrasher_wrapper_ft_send_cancel(guint id) {
1429 /* @exception bail if our callback doesn't exist */
1430 g_return_if_fail(ft_callback.ft_send_cancel_cb != NULL);
1432 dSP;
1434 ENTER;
1435 SAVETMPS;
1437 PUSHMARK(sp);
1439 /* Push the args onto the stack */
1440 XPUSHs(sv_2mortal(newSViv(id)));
1442 PUTBACK;
1444 call_sv(ft_callback.ft_send_cancel_cb, G_EVAL | G_SCALAR);
1446 SPAGAIN;
1448 FREETMPS;
1449 PUTBACK;
1451 LEAVE;
1453 return;
1457 * @brief Called on libpurple file-send-complete signal
1458 * @param id of Thrasher file transfer structure.
1460 void
1461 thrasher_wrapper_ft_send_complete(guint id) {
1462 /* @exception bail if our callback doesn't exist */
1463 g_return_if_fail(ft_callback.ft_send_complete_cb != NULL);
1465 dSP;
1467 ENTER;
1468 SAVETMPS;
1470 PUSHMARK(sp);
1472 /* Push the args onto the stack */
1473 XPUSHs(sv_2mortal(newSViv(id)));
1475 PUTBACK;
1477 call_sv(ft_callback.ft_send_complete_cb, G_EVAL | G_SCALAR);
1479 SPAGAIN;
1481 FREETMPS;
1482 PUTBACK;
1484 LEAVE;
1486 return;
1489 gssize
1490 thrasher_wrapper_ft_read(guint id,
1491 guchar **buffer,
1492 gssize size) {
1493 int read_sz = -1;
1494 int count = 0;
1496 /* @exception bail if our callback doesn't exist */
1497 g_return_val_if_fail(ft_callback.ft_read_cb != NULL, 0);
1499 dSP;
1501 ENTER;
1502 SAVETMPS;
1504 PUSHMARK(sp);
1506 /* Push the args onto the stack */
1507 XPUSHs(sv_2mortal(newSViv(id)));
1508 XPUSHs(sv_2mortal(newSViv(size)));
1510 PUTBACK;
1512 count = call_sv(ft_callback.ft_read_cb, G_EVAL | G_ARRAY);
1514 SPAGAIN;
1516 if (count == 2) {
1517 read_sz = POPl;
1518 void* read = POPpbytex;
1519 *buffer = malloc(read_sz);
1520 /* This buffer is free'd by libpurple. */
1521 memcpy(*buffer, read, read_sz);
1523 else {
1524 *buffer = NULL;
1525 read_sz = -1;
1528 FREETMPS;
1529 PUTBACK;
1531 LEAVE;
1533 return read_sz;
1536 void
1537 thrasher_wrapper_ft_data_not_sent(guint id,
1538 const char *buffer,
1539 gsize size) {
1540 /* @exception buffer cannot be NULL */
1541 g_return_if_fail(buffer != NULL);
1543 /* @exception bail if our callback doesn't exist */
1544 g_return_if_fail(ft_callback.ft_data_not_sent_cb != NULL);
1546 dSP;
1548 ENTER;
1549 SAVETMPS;
1551 PUSHMARK(sp);
1553 /* Push the args onto the stack */
1554 XPUSHs(sv_2mortal(newSViv(id)));
1555 /* buffer is not null-terminated. */
1556 XPUSHs(sv_2mortal(newSVpvn(buffer, size)));
1558 PUTBACK;
1560 call_sv(ft_callback.ft_data_not_sent_cb, G_EVAL | G_SCALAR);
1562 SPAGAIN;
1564 FREETMPS;
1565 PUTBACK;
1567 LEAVE;
1569 return;
1573 * @brief ask user's permission for ft and set up bytestream
1574 * @param xfer a filled-in PURPLE_XFER_RECEIVE PurpleXfer.
1575 * @param filename suggested local filename
1576 * @return nothing; use thrasher_action_ft_recv_request_respond when ready
1578 void
1579 thrasher_wrapper_ft_recv_request(PurpleXfer *xfer,
1580 const char* filename) {
1581 /* @exception xfer cannot be NULL */
1582 g_return_if_fail(xfer != NULL);
1584 /* @exception bail if our callback doesn't exist */
1585 g_return_if_fail(ft_callback.ft_recv_request_cb != NULL);
1587 dSP;
1589 ENTER;
1590 SAVETMPS;
1592 PUSHMARK(sp);
1594 /* Push the args onto the stack */
1595 guint id = get_id_by_xfer(xfer);
1596 XPUSHs(sv_2mortal(newSViv(id)));
1597 PurpleAccount* pa = purple_xfer_get_account(xfer);
1598 gchar* jid = thrasher_account_get_jid(pa);
1599 XPUSHs(clean_newSVpv(jid));
1600 const char* who = purple_xfer_get_remote_user(xfer);
1601 XPUSHs(clean_newSVpv(who));
1602 XPUSHs(clean_newSVpv(filename));
1603 size_t size = purple_xfer_get_size(xfer);
1604 XPUSHs(sv_2mortal(newSViv(size)));
1606 PUTBACK;
1608 call_sv(ft_callback.ft_recv_request_cb, G_EVAL | G_SCALAR);
1610 SPAGAIN;
1612 FREETMPS;
1613 PUTBACK;
1615 LEAVE;
1617 return;
1620 void
1621 thrasher_action_ft_recv_request_respond(size_t id,
1622 unsigned int accept) {
1623 thrasher_xfer_recv_request_responder(id, accept);
1626 gssize
1627 thrasher_wrapper_ft_write(guint id,
1628 const char *buffer,
1629 gssize size) {
1630 int written_sz = -1;
1632 /* @exception bail if our callback doesn't exist */
1633 g_return_val_if_fail(ft_callback.ft_write_cb != NULL, 0);
1635 dSP;
1637 ENTER;
1638 SAVETMPS;
1640 PUSHMARK(sp);
1642 /* Push the args onto the stack */
1643 XPUSHs(sv_2mortal(newSViv(id)));
1644 /* buffer is not null-terminated. */
1645 XPUSHs(sv_2mortal(newSVpvn(buffer, size)));
1647 PUTBACK;
1649 call_sv(ft_callback.ft_write_cb, G_EVAL | G_ARRAY);
1651 SPAGAIN;
1653 written_sz = POPl;
1655 FREETMPS;
1656 PUTBACK;
1658 LEAVE;
1660 return written_sz;
1664 * @brief Called on libpurple file-recv-cancel signal
1665 * @param id of Thrasher file transfer structure.
1667 void
1668 thrasher_wrapper_ft_recv_cancel(guint id) {
1669 /* @exception bail if our callback doesn't exist */
1670 g_return_if_fail(ft_callback.ft_recv_cancel_cb != NULL);
1672 dSP;
1674 ENTER;
1675 SAVETMPS;
1677 PUSHMARK(sp);
1679 /* Push the args onto the stack */
1680 XPUSHs(sv_2mortal(newSViv(id)));
1682 PUTBACK;
1684 call_sv(ft_callback.ft_recv_cancel_cb, G_EVAL | G_SCALAR);
1686 SPAGAIN;
1688 FREETMPS;
1689 PUTBACK;
1691 LEAVE;
1693 return;
1697 * @brief Called on libpurple file-recv-complete signal
1698 * @param id of Thrasher file transfer structure.
1700 void
1701 thrasher_wrapper_ft_recv_complete(guint id) {
1702 /* @exception bail if our callback doesn't exist */
1703 g_return_if_fail(ft_callback.ft_recv_complete_cb != NULL);
1705 dSP;
1707 ENTER;
1708 SAVETMPS;
1710 PUSHMARK(sp);
1712 /* Push the args onto the stack */
1713 XPUSHs(sv_2mortal(newSViv(id)));
1715 PUTBACK;
1717 call_sv(ft_callback.ft_recv_complete_cb, G_EVAL | G_SCALAR);
1719 SPAGAIN;
1721 FREETMPS;
1722 PUTBACK;
1724 LEAVE;
1726 return;
1729 void
1730 thrasher_action_ft_cancel_local(size_t id) {
1731 PurpleXfer* xfer = get_xfer_by_id(id);
1732 purple_xfer_cancel_local(xfer);
1735 #ifdef TH_DEBUG
1736 /*********************************************************
1738 * Only debugging stuff beyond this point!
1740 *********************************************************/
1741 gboolean foo_callback (void *data);
1742 gboolean foo_callback2 (void *data);
1743 void foo_callback3 (gpointer data, gint fd, PurpleInputCondition cond);
1744 long thrasher_wrapper_trigger_timeout_add (void);
1745 long thrasher_wrapper_trigger_timeout_add2 (void);
1746 long thrasher_wrapper_trigger_timeout_add3 (void);
1747 long thrasher_wrapper_trigger_input_add (void);
1748 void thrasher_wrapper_dump_calls (void);
1749 void spit_func_info (gpointer key, gpointer value, gpointer user_data);
1752 gboolean
1753 foo_callback (void *data)
1755 printf("!!!!!!Calling a callback w/ data [%s]!!!!\n", (char *)data);
1756 return TRUE;
1760 int cntr = 9;
1763 gboolean
1764 foo_callback2 (void *data)
1766 printf("will die in [%d] cycles\n", cntr);
1768 if (cntr--)
1769 return TRUE;
1770 else
1771 return FALSE;
1774 void foo_callback3 (gpointer data, gint fd, PurpleInputCondition cond)
1776 printf("data [%p]\tfd [%d]\tcond [%d]\n", data, fd, cond);
1779 /* Test functions to allow an external caller to trigger internal actions */
1780 long thrasher_wrapper_trigger_timeout_add ()
1782 printf("trigger_timeout_add called\n");
1783 return thrasher_wrapper_set_timeout_add(1234, foo_callback, "YAY! test data");
1787 long thrasher_wrapper_trigger_timeout_add2 ()
1789 printf("trigger_timeout_add2 called\n");
1790 return thrasher_wrapper_set_timeout_add(1867, foo_callback, "test data says what?");
1793 long thrasher_wrapper_trigger_timeout_add3 ()
1795 printf("trigger_timeout_add3 called\n");
1796 return thrasher_wrapper_set_timeout_add(200, foo_callback2, "this shoudl fail shortly");
1799 long thrasher_wrapper_trigger_input_add ()
1801 printf("trigger_input_add called\n");
1803 int fd = open("/tmp/testfoo", O_RDWR);
1804 PurpleInputCondition cond;
1805 cond = PURPLE_INPUT_WRITE;
1806 long face = 0;
1807 printf("HAMMERTIME\n");
1808 face = thrasher_wrapper_set_input_add(fd, cond, foo_callback3, "double plus win ungood");
1810 return face;
1813 int thrasher_wrapper_trigger_timeout_remove (long key)
1815 printf("trigger_timeout_remove called\n");
1816 /* We currently have no way to correlate external keys to internal keys... */
1817 return !thrasher_wrapper_call_source_remove(key);
1821 void spit_func_info (gpointer key, gpointer value, gpointer user_data)
1823 hash_record *record;
1824 printf("key [%p]\tvalue [%p]\n", key, value);
1826 record = key;
1828 if (record->type == INPUT_ADD)
1830 printf("\tINPUT_ADD\n\tfunction [%p]\n\tdata [%p]\n\tcond [%d]\n\tfd [%d]\n", record->function, record->data, record->cond, record->fd);
1833 else if (record->type == TIMEOUT_ADD)
1836 printf("\tTIMEOUT_ADD\n\tfunction [%p]\n\tdata [%p]\n", record->function, record->data);
1839 else
1841 printf("record type [%d] is unrecognized!\n", record->type);
1845 void thrasher_wrapper_dump_calls ()
1847 g_hash_table_foreach(callback_funcs, spit_func_info, NULL);
1850 #endif /* TH_DEBUG */