Translate incoming typing notifications from libpurple into chatstates.
[thrasher.git] / thrasher.c
blob17fd234e14233266afcd8e31b63cfb07f67fe5ce
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 <signal.h>
21 #include <string.h>
22 #include <unistd.h>
23 #include <sys/socket.h>
25 #include "purple.h"
26 #include "thrasher.h"
27 #include "thblist.h"
28 #include "thconnection.h"
29 #include "thperl.h"
30 #include "thft.h"
31 #include "threquest.h"
32 #include "thconversations.h"
34 GHashTable *account_to_connection = NULL;
35 GHashTable *jid_to_account = NULL;
37 void debug_print(PurpleDebugLevel level, const char *category, const char *arg_s);
38 gboolean is_enabled(PurpleDebugLevel level, const char *category);
40 static PurpleEventLoopUiOps glib_eventloops =
42 thrasher_wrapper_set_timeout_add,
43 thrasher_wrapper_call_source_remove,
44 thrasher_wrapper_set_input_add,
45 thrasher_wrapper_call_source_remove,
46 NULL,
47 NULL,
49 /* padding */
50 NULL,
51 NULL,
52 NULL
54 static PurpleCoreUiOps thrasher_core_uiops =
56 NULL,
57 NULL,
58 NULL,
59 NULL,
61 /* padding */
62 NULL,
63 NULL,
64 NULL,
65 NULL
69 static PurpleConversationUiOps thrasher_conv_uiops =
71 NULL, /* create_conversation */
72 NULL, /* destroy_conversation */
73 NULL, /* write_chat */
74 NULL, /* write_im */
75 thrasher_write_conv, /* write_conv */
76 NULL, /* chat_add_users */
77 NULL, /* chat_rename_user */
78 NULL, /* chat_remove_users */
79 NULL, /* chat_update_user */
80 NULL, /* present */
81 NULL, /* has_focus */
82 NULL, /* custom_smiley_add */
83 NULL, /* custom_smiley_write */
84 NULL, /* custom_smiley_close */
85 NULL, /* send_confirm */
86 NULL,
87 NULL,
88 NULL,
89 NULL
92 static PurpleConnectionUiOps thrasher_conn_uiops =
94 // connect progress
95 NULL,
96 // connected,
97 thrasher_connection,
98 // disconnected
99 NULL,
100 // connection-specific notices, effectively unused
101 NULL,
102 // report disconnect
103 NULL,
104 // computer network connected
105 NULL,
106 // computer network disconnected
107 NULL,
108 // report disconnect reason
109 NULL,
110 // reserved 1
111 NULL,
112 // reserved 2
113 NULL,
114 // reserved 3
115 NULL
118 void thrasher_init()
120 purple_core_set_ui_ops(&thrasher_core_uiops);
121 purple_eventloop_set_ui_ops(&glib_eventloops);
122 purple_conversations_set_ui_ops(&thrasher_conv_uiops);
123 purple_connections_set_ui_ops(&thrasher_conn_uiops);
125 if (!purple_core_init(UI_ID))
126 error(EXIT_FAILURE, 0, "libpurple init failed");
128 thrasher_blist_init();
129 thrasher_connection_init();
130 thrasher_xfer_init();
131 thrasher_perl_init();
132 thrasher_request_init();
133 thrasher_conversations_init();
135 // This hash table has no ownership on the pointers stored in it
136 account_to_connection = g_hash_table_new(g_direct_hash,
137 g_direct_equal);
138 // Automatically cleans up keys, does not own stored pointers
139 jid_to_account = g_hash_table_new_full(g_str_hash, g_str_equal,
140 g_free, NULL);
142 purple_set_blist(purple_blist_new());
144 purple_signal_connect(purple_connections_get_handle(),
145 "signing-off",
146 thrasher_connection_get_handle(),
147 PURPLE_CALLBACK(thrasher_signing_off_cb),
148 NULL);
151 void thrasher_whoami (PurpleAccount * pa)
153 if (pa)
154 purple_debug_info("thrasher",
155 "You are user [%s]\n",
156 pa->username);
159 void thrasher_signing_off_cb(PurpleConnection *gc) {
160 g_return_if_fail(gc);
162 PurpleAccount* pa = purple_connection_get_account(gc);
163 g_return_if_fail(pa);
165 if (g_hash_table_lookup(account_to_connection, pa)) {
166 char *jid = thrasher_account_get_jid(pa);
167 g_hash_table_remove(jid_to_account, jid);
168 g_hash_table_remove(account_to_connection, pa);
172 void
173 thrasher_logout (PurpleAccount *pa)
175 g_return_if_fail(pa);
177 purple_account_set_enabled(pa, UI_ID, FALSE);
179 // Only disconnect if we haven't already
180 if (g_hash_table_lookup(account_to_connection, pa)) {
181 purple_account_disconnect(pa);
182 } else {
183 purple_debug_info("thrasher",
184 "Declined to log out unconnected account\n");
187 if (pa->ui_data) {
188 Thrasher_PurpleAccount_UI_Data* ui_data = pa->ui_data;
189 pa->ui_data = NULL;
190 g_hash_table_destroy(ui_data->pending_send_file);
191 g_free(ui_data);
195 PurpleAccount * thrasher_get_account_by_jid(const char* jid) {
196 g_return_val_if_fail(jid, NULL);
197 return g_hash_table_lookup(jid_to_account, jid);
200 PurpleConnection * thrasher_get_connection_by_account
201 (const PurpleAccount* account) {
202 g_return_val_if_fail(account, NULL);
203 return g_hash_table_lookup(account_to_connection, account);
206 // An error can occur during the login, when the protocol decides
207 // there's going to be a connection error before it even returns the
208 // connection to us. In that case, our user data won't exist on the
209 // connection, but the connection error still needs to propagate back
210 // to the user. In that case, we use this value. This rather ties
211 // us to a single thread.
213 static char *current_login_jid = NULL;
214 static uint got_error = 0;
216 char * get_current_login_jid () {
217 return current_login_jid;
220 void set_got_error (uint new_val) {
221 got_error = new_val;
224 PurpleAccount *
225 thrasher_login (char *service, char *username, char *password, char *jid,
226 GHashTable *other_args)
228 got_error = 0;
229 current_login_jid = jid;
230 gchar *user_dir;
232 if (purple_get_core() == 0)
233 error(EXIT_FAILURE, 0, "Purple core is uninitialized");
235 purple_debug_info("thrasher",
236 "Thrasher login initiated\n");
238 /* Test service for validity */
240 /* Test user for validity */
241 /* We need to verify there is NO path redirection in the user name! */
242 /* We also need to verify username is NOT longer than MAX_NAME_LEN */
244 /* Test password for validity */
247 /* Validate the root_dir and build us a filename */
248 user_dir = g_build_filename(USER_ROOT_DIR, service, username, NULL);
250 purple_util_set_user_dir(user_dir);
252 g_free(user_dir);
254 PurpleAccount *account;
255 PurpleSavedStatus *status;
257 /* Need to research these... */
258 purple_blist_load();
259 purple_prefs_load();
260 purple_pounces_load(); /* Don't believe we need this */
262 /* Setup the account and throw it back */
263 account = purple_account_new(username, service);
264 g_hash_table_insert(jid_to_account, g_strdup(jid), account);
265 purple_account_set_password(account, password);
267 Thrasher_PurpleAccount_UI_Data* ui_data
268 = g_new(Thrasher_PurpleAccount_UI_Data, 1);
269 ui_data->jid = g_strdup(jid); /* Store the JID with the account */
270 ui_data->pending_send_file = g_hash_table_new(g_str_hash,
271 g_str_equal);
272 account->ui_data = ui_data;
274 if (other_args) {
275 GHashTableIter iter;
276 gpointer key, value;
277 char *char_key, *char_value;
279 g_hash_table_iter_init(&iter, other_args);
280 while (g_hash_table_iter_next(&iter, &key, &value)) {
281 char_value = (char *)value;
282 char_key = (char *)key;
283 if (!strncmp(key, "int_", 4))
285 char_key += 4;
286 int int_value = atoi(char_value);
287 purple_debug_info("thrasher",
288 "Setting account #: %s -> %d\n",
289 char_key, int_value);
290 purple_account_set_int(account, char_key, atoi(char_value));
291 } else if (!strncmp(key, "bool_", 5)) {
292 char_key += 5;
293 int value = (*char_value == '0' ||
294 *char_value == 0) ? FALSE: TRUE;
295 purple_debug_info("thrasher",
296 "Setting account bool: %s -> %d\n",
297 char_key, value);
298 purple_account_set_bool(account, char_key, value);
299 } else {
300 purple_debug_info("thrasher",
301 "Setting account string: %s -> %s\n",
302 char_key, char_value);
303 purple_account_set_string(account, char_key,
304 char_value);
309 purple_account_set_enabled(account, UI_ID, TRUE);
310 status = purple_savedstatus_new(NULL, PURPLE_STATUS_AVAILABLE);
311 purple_savedstatus_activate(status);
313 current_login_jid = NULL;
315 if (got_error) {
316 purple_debug_info("thrasher",
317 "got an error during initial login\n");
318 return NULL;
321 #ifdef TH_DEBUG
322 purple_debug_info("thrasher",
323 "Supported protocols\n");
324 GList *iter; int i;
325 iter = purple_plugins_get_protocols();
326 for (i = 0; iter; iter = iter->next) {
327 PurplePlugin *plugin = iter->data;
328 PurplePluginInfo *info = plugin->info;
329 if (info && info->name) {
330 purple_debug_info("thrasher",
331 "\t%d: %s\n",
332 i++,
333 info->name);
336 #endif
339 /* Not sure how I got in this state, but write a catch nonetheless */
340 if (!purple_plugins_get_protocols())
341 error(EXIT_FAILURE, 0, "libpurple reports NO supported protocols, this is a BAD thing");
343 return account;
347 * How to send message to remote user
349 * pa = PurpleAccount ptr
350 * name = recipient name char*
351 * message = message char*
353 * If the conversation data can be created this returns TRUE, else it returns FALSE
355 * Get the conversation if it already exists
356 * (Per the code flow, we may be able to set type to NULL and let it figure it out.)
359 gboolean
360 thrasher_send_msg (PurpleAccount *pa, const char *name, const char *message)
362 PurpleConversation *conv;
363 PurpleConvIm *im;
365 /* For testing and the initial build we're recreating a conversation *every time* */
366 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, name, pa);
368 /* If not, create a new one */
369 if (conv == NULL)
370 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, pa, name);
372 /* Get the IM specific data */
373 im = purple_conversation_get_im_data(conv);
375 if (im) {
376 purple_conv_im_send(im, message);
377 return TRUE;
380 purple_debug_info("thrasher",
381 "Failed in purple_conv_im_send\n");
383 return FALSE;
386 void thrasher_connection (PurpleConnection *conn)
388 PurpleAccount *account = purple_connection_get_account(conn);
390 g_hash_table_insert(account_to_connection, account, conn);
392 thrasher_wrapper_connection(thrasher_account_get_jid(account));
395 PurpleConnection* thrasher_connection_for_account(PurpleAccount
396 *account) {
397 g_return_val_if_fail(account, NULL);
399 return g_hash_table_lookup(account_to_connection, account);
402 void thrasher_write_conv (PurpleConversation *conv, const char *who, const char *alias,
403 const char *message, PurpleMessageFlags flags, time_t mtime)
405 PurpleAccount *account;
406 account = purple_conversation_get_account(conv);
408 /* libpurple throws write_conv on every recv and send */
409 if (flags != PURPLE_MESSAGE_RECV)
410 return;
412 purple_debug_info("thrasher",
413 "Incoming message:\n");
415 thrasher_wrapper_incoming_msg(thrasher_account_get_jid(account),
416 who,
417 alias,
418 message);
420 #ifdef TH_DEBUG
421 purple_debug_info("thrasher",
422 "(%s) %s %s: %s\n",
423 purple_conversation_get_name(conv),
424 purple_utf8_strftime("(%H:%M:%S)", localtime(&mtime)),
425 who,
426 message);
427 #endif /* TH_DEBUG */
431 * thrasher_account_get_jid
433 gchar*
434 thrasher_account_get_jid(PurpleAccount *account)
436 g_return_val_if_fail(account, NULL);
438 Thrasher_PurpleAccount_UI_Data* ui_data = account->ui_data;
439 return ui_data->jid;
443 * thrasher_account_get_pending_send_file
445 GHashTable*
446 thrasher_account_get_pending_send_file(PurpleAccount *account) {
447 g_return_val_if_fail(account, NULL);
449 Thrasher_PurpleAccount_UI_Data* ui_data = account->ui_data;
450 g_return_val_if_fail(ui_data, NULL);
451 return ui_data->pending_send_file;