| |
1 |
| |
2 /* |
| |
3 Meanwhile Protocol Plugin for Gaim |
| |
4 Adds Lotus Sametime support to Gaim using the Meanwhile library |
| |
5 |
| |
6 Copyright (C) 2004 Christopher (siege) O'Brien <siege@preoccupied.net> |
| |
7 |
| |
8 This program is free software; you can redistribute it and/or modify |
| |
9 it under the terms of the GNU General Public License as published by |
| |
10 the Free Software Foundation; either version 2 of the License, or (at |
| |
11 your option) any later version. |
| |
12 |
| |
13 This program is distributed in the hope that it will be useful, but |
| |
14 WITHOUT ANY WARRANTY; without even the implied warranty of |
| |
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| |
16 General Public License for more details. |
| |
17 |
| |
18 You should have received a copy of the GNU General Public License |
| |
19 along with this program; if not, write to the Free Software |
| |
20 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
| |
21 USA. |
| |
22 */ |
| |
23 |
| |
24 |
| |
25 /* system includes */ |
| |
26 #include <stdlib.h> |
| |
27 #include <time.h> |
| |
28 |
| |
29 /* glib includes */ |
| |
30 #include <glib.h> |
| |
31 #include <glib/ghash.h> |
| |
32 #include <glib/glist.h> |
| |
33 |
| |
34 /* gaim includes */ |
| |
35 #include "internal.h" |
| |
36 #include "config.h" |
| |
37 |
| |
38 #include "account.h" |
| |
39 #include "accountopt.h" |
| |
40 #include "circbuffer.h" |
| |
41 #include "conversation.h" |
| |
42 #include "debug.h" |
| |
43 #include "ft.h" |
| |
44 #include "imgstore.h" |
| |
45 #include "mime.h" |
| |
46 #include "notify.h" |
| |
47 #include "plugin.h" |
| |
48 #include "privacy.h" |
| |
49 #include "prpl.h" |
| |
50 #include "request.h" |
| |
51 #include "util.h" |
| |
52 #include "version.h" |
| |
53 |
| |
54 /* meanwhile includes */ |
| |
55 #include <mw_cipher.h> |
| |
56 #include <mw_common.h> |
| |
57 #include <mw_error.h> |
| |
58 #include <mw_service.h> |
| |
59 #include <mw_session.h> |
| |
60 #include <mw_srvc_aware.h> |
| |
61 #include <mw_srvc_conf.h> |
| |
62 #include <mw_srvc_ft.h> |
| |
63 #include <mw_srvc_im.h> |
| |
64 #include <mw_srvc_place.h> |
| |
65 #include <mw_srvc_resolve.h> |
| |
66 #include <mw_srvc_store.h> |
| |
67 #include <mw_st_list.h> |
| |
68 |
| |
69 /* plugin includes */ |
| |
70 #include "sametime.h" |
| |
71 |
| |
72 |
| |
73 /* considering that there's no display of this information for prpls, |
| |
74 I don't know why I even bother providing these. Oh valiant reader, |
| |
75 I do it all for you. */ |
| |
76 /* scratch that, I just added it to the prpl options panel */ |
| |
77 #define PLUGIN_ID "prpl-meanwhile" |
| |
78 #define PLUGIN_NAME "Sametime" |
| |
79 #define PLUGIN_SUMMARY "Sametime Protocol Plugin" |
| |
80 #define PLUGIN_DESC "Open implementation of a Lotus Sametime client" |
| |
81 #define PLUGIN_AUTHOR "Christopher (siege) O'Brien <siege@preoccupied.net>" |
| |
82 #define PLUGIN_HOMEPAGE "http://meanwhile.sourceforge.net/" |
| |
83 |
| |
84 |
| |
85 /* plugin preference names */ |
| |
86 #define MW_PRPL_OPT_BASE "/plugins/prpl/meanwhile" |
| |
87 #define MW_PRPL_OPT_BLIST_ACTION MW_PRPL_OPT_BASE "/blist_action" |
| |
88 #define MW_PRPL_OPT_PSYCHIC MW_PRPL_OPT_BASE "/psychic" |
| |
89 #define MW_PRPL_OPT_FORCE_LOGIN MW_PRPL_OPT_BASE "/force_login" |
| |
90 #define MW_PRPL_OPT_SAVE_DYNAMIC MW_PRPL_OPT_BASE "/save_dynamic" |
| |
91 |
| |
92 |
| |
93 /* stages of connecting-ness */ |
| |
94 #define MW_CONNECT_STEPS 11 |
| |
95 |
| |
96 |
| |
97 /* stages of conciousness */ |
| |
98 #define MW_STATE_OFFLINE "offline" |
| |
99 #define MW_STATE_ACTIVE "active" |
| |
100 #define MW_STATE_AWAY "away" |
| |
101 #define MW_STATE_BUSY "dnd" |
| |
102 #define MW_STATE_MESSAGE "message" |
| |
103 #define MW_STATE_ENLIGHTENED "buddha" |
| |
104 |
| |
105 |
| |
106 /* keys to get/set chat information */ |
| |
107 #define CHAT_KEY_CREATOR "chat.creator" |
| |
108 #define CHAT_KEY_NAME "chat.name" |
| |
109 #define CHAT_KEY_TOPIC "chat.topic" |
| |
110 #define CHAT_KEY_INVITE "chat.invite" |
| |
111 #define CHAT_KEY_IS_PLACE "chat.is_place" |
| |
112 |
| |
113 |
| |
114 /* key for associating a mwLoginType with a buddy */ |
| |
115 #define BUDDY_KEY_CLIENT "meanwhile.client" |
| |
116 |
| |
117 /* store the remote alias so that we can re-create it easily */ |
| |
118 #define BUDDY_KEY_NAME "meanwhile.shortname" |
| |
119 |
| |
120 /* enum mwSametimeUserType */ |
| |
121 #define BUDDY_KEY_TYPE "meanwhile.type" |
| |
122 |
| |
123 |
| |
124 /* key for the real group name for a meanwhile group */ |
| |
125 #define GROUP_KEY_NAME "meanwhile.group" |
| |
126 |
| |
127 /* enum mwSametimeGroupType */ |
| |
128 #define GROUP_KEY_TYPE "meanwhile.type" |
| |
129 |
| |
130 /* NAB group owning account */ |
| |
131 #define GROUP_KEY_OWNER "meanwhile.account" |
| |
132 |
| |
133 /* key gtk blist uses to indicate a collapsed group */ |
| |
134 #define GROUP_KEY_COLLAPSED "collapsed" |
| |
135 |
| |
136 |
| |
137 /* verification replacement */ |
| |
138 #define mwSession_NO_SECRET "meanwhile.no_secret" |
| |
139 |
| |
140 |
| |
141 /* keys to get/set gaim plugin information */ |
| |
142 #define MW_KEY_HOST "server" |
| |
143 #define MW_KEY_PORT "port" |
| |
144 #define MW_KEY_FORCE "force_login" |
| |
145 #define MW_KEY_FAKE_IT "fake_client_id" |
| |
146 #define MW_KEY_CLIENT "client_id_val" |
| |
147 #define MW_KEY_MAJOR "client_major" |
| |
148 #define MW_KEY_MINOR "client_minor" |
| |
149 |
| |
150 |
| |
151 /** number of seconds from the first blist change before a save to the |
| |
152 storage service occurs. */ |
| |
153 #define BLIST_SAVE_SECONDS 15 |
| |
154 |
| |
155 |
| |
156 /** the possible buddy list storage settings */ |
| |
157 enum blist_choice { |
| |
158 blist_choice_LOCAL = 1, /**< local only */ |
| |
159 blist_choice_MERGE = 2, /**< merge from server */ |
| |
160 blist_choice_STORE = 3, /**< merge from and save to server */ |
| |
161 blist_choice_SYNCH = 4, /**< sync with server */ |
| |
162 }; |
| |
163 |
| |
164 |
| |
165 /** the default blist storage option */ |
| |
166 #define BLIST_CHOICE_DEFAULT blist_choice_SYNCH |
| |
167 |
| |
168 |
| |
169 /* testing for the above */ |
| |
170 #define BLIST_PREF_IS(n) (gaim_prefs_get_int(MW_PRPL_OPT_BLIST_ACTION)==(n)) |
| |
171 #define BLIST_PREF_IS_LOCAL() BLIST_PREF_IS(blist_choice_LOCAL) |
| |
172 #define BLIST_PREF_IS_MERGE() BLIST_PREF_IS(blist_choice_MERGE) |
| |
173 #define BLIST_PREF_IS_STORE() BLIST_PREF_IS(blist_choice_STORE) |
| |
174 #define BLIST_PREF_IS_SYNCH() BLIST_PREF_IS(blist_choice_SYNCH) |
| |
175 |
| |
176 |
| |
177 /* debugging output */ |
| |
178 #define DEBUG_ERROR(a...) gaim_debug_error(G_LOG_DOMAIN, a) |
| |
179 #define DEBUG_INFO(a...) gaim_debug_info(G_LOG_DOMAIN, a) |
| |
180 #define DEBUG_MISC(a...) gaim_debug_misc(G_LOG_DOMAIN, a) |
| |
181 #define DEBUG_WARN(a...) gaim_debug_warning(G_LOG_DOMAIN, a) |
| |
182 |
| |
183 |
| |
184 /** ensure non-null strings */ |
| |
185 #ifndef NSTR |
| |
186 # define NSTR(str) ((str)? (str): "(null)") |
| |
187 #endif |
| |
188 |
| |
189 |
| |
190 /** calibrates distinct secure channel nomenclature */ |
| |
191 static const unsigned char no_secret[] = { |
| |
192 0x2d, 0x2d, 0x20, 0x73, 0x69, 0x65, 0x67, 0x65, |
| |
193 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x73, 0x20, 0x6a, |
| |
194 0x65, 0x6e, 0x6e, 0x69, 0x20, 0x61, 0x6e, 0x64, |
| |
195 0x20, 0x7a, 0x6f, 0x65, 0x20, 0x2d, 0x2d, 0x00, |
| |
196 }; |
| |
197 |
| |
198 |
| |
199 /** handler IDs from g_log_set_handler in mw_plugin_init */ |
| |
200 static guint log_handler[2] = { 0, 0 }; |
| |
201 |
| |
202 |
| |
203 /** the gaim plugin data. |
| |
204 available as gc->proto_data and mwSession_getClientData */ |
| |
205 struct mwGaimPluginData { |
| |
206 struct mwSession *session; |
| |
207 |
| |
208 struct mwServiceAware *srvc_aware; |
| |
209 struct mwServiceConference *srvc_conf; |
| |
210 struct mwServiceFileTransfer *srvc_ft; |
| |
211 struct mwServiceIm *srvc_im; |
| |
212 struct mwServicePlace *srvc_place; |
| |
213 struct mwServiceResolve *srvc_resolve; |
| |
214 struct mwServiceStorage *srvc_store; |
| |
215 |
| |
216 /** map of GaimGroup:mwAwareList and mwAwareList:GaimGroup */ |
| |
217 GHashTable *group_list_map; |
| |
218 |
| |
219 /** event id for the buddy list save callback */ |
| |
220 guint save_event; |
| |
221 |
| |
222 /** socket fd */ |
| |
223 int socket; |
| |
224 gint outpa; /* like inpa, but the other way */ |
| |
225 |
| |
226 /** circular buffer for outgoing data */ |
| |
227 GaimCircBuffer *sock_buf; |
| |
228 |
| |
229 GaimConnection *gc; |
| |
230 }; |
| |
231 |
| |
232 |
| |
233 typedef struct { |
| |
234 GaimBuddy *buddy; |
| |
235 GaimGroup *group; |
| |
236 } BuddyAddData; |
| |
237 |
| |
238 |
| |
239 /* blist and aware functions */ |
| |
240 |
| |
241 static void blist_export(GaimConnection *gc, struct mwSametimeList *stlist); |
| |
242 |
| |
243 static void blist_store(struct mwGaimPluginData *pd); |
| |
244 |
| |
245 static void blist_schedule(struct mwGaimPluginData *pd); |
| |
246 |
| |
247 static void blist_merge(GaimConnection *gc, struct mwSametimeList *stlist); |
| |
248 |
| |
249 static void blist_sync(GaimConnection *gc, struct mwSametimeList *stlist); |
| |
250 |
| |
251 static gboolean buddy_is_external(GaimBuddy *b); |
| |
252 |
| |
253 static void buddy_add(struct mwGaimPluginData *pd, GaimBuddy *buddy); |
| |
254 |
| |
255 static GaimBuddy * |
| |
256 buddy_ensure(GaimConnection *gc, GaimGroup *group, |
| |
257 struct mwSametimeUser *stuser); |
| |
258 |
| |
259 static void group_add(struct mwGaimPluginData *pd, GaimGroup *group); |
| |
260 |
| |
261 static GaimGroup * |
| |
262 group_ensure(GaimConnection *gc, struct mwSametimeGroup *stgroup); |
| |
263 |
| |
264 static struct mwAwareList * |
| |
265 list_ensure(struct mwGaimPluginData *pd, GaimGroup *group); |
| |
266 |
| |
267 |
| |
268 /* session functions */ |
| |
269 |
| |
270 static struct mwSession * |
| |
271 gc_to_session(GaimConnection *gc); |
| |
272 |
| |
273 static GaimConnection *session_to_gc(struct mwSession *session); |
| |
274 |
| |
275 |
| |
276 /* conference functions */ |
| |
277 |
| |
278 static struct mwConference * |
| |
279 conf_find_by_id(struct mwGaimPluginData *pd, int id); |
| |
280 |
| |
281 |
| |
282 /* conversation functions */ |
| |
283 |
| |
284 struct convo_msg { |
| |
285 enum mwImSendType type; |
| |
286 gpointer data; |
| |
287 GDestroyNotify clear; |
| |
288 }; |
| |
289 |
| |
290 |
| |
291 struct convo_data { |
| |
292 struct mwConversation *conv; |
| |
293 GList *queue; /**< outgoing message queue, list of convo_msg */ |
| |
294 }; |
| |
295 |
| |
296 static void convo_data_new(struct mwConversation *conv); |
| |
297 |
| |
298 static void convo_data_free(struct convo_data *conv); |
| |
299 |
| |
300 static void convo_features(struct mwConversation *conv); |
| |
301 |
| |
302 static GaimConversation *convo_get_gconv(struct mwConversation *conv); |
| |
303 |
| |
304 |
| |
305 /* name and id */ |
| |
306 |
| |
307 struct named_id { |
| |
308 char *id; |
| |
309 char *name; |
| |
310 }; |
| |
311 |
| |
312 |
| |
313 /* connection functions */ |
| |
314 |
| |
315 static void connect_cb(gpointer data, gint source, const gchar *error_message); |
| |
316 |
| |
317 |
| |
318 /* ----- session ------ */ |
| |
319 |
| |
320 |
| |
321 /** resolves a mwSession from a GaimConnection */ |
| |
322 static struct mwSession *gc_to_session(GaimConnection *gc) { |
| |
323 struct mwGaimPluginData *pd; |
| |
324 |
| |
325 g_return_val_if_fail(gc != NULL, NULL); |
| |
326 |
| |
327 pd = gc->proto_data; |
| |
328 g_return_val_if_fail(pd != NULL, NULL); |
| |
329 |
| |
330 return pd->session; |
| |
331 } |
| |
332 |
| |
333 |
| |
334 /** resolves a GaimConnection from a mwSession */ |
| |
335 static GaimConnection *session_to_gc(struct mwSession *session) { |
| |
336 struct mwGaimPluginData *pd; |
| |
337 |
| |
338 g_return_val_if_fail(session != NULL, NULL); |
| |
339 |
| |
340 pd = mwSession_getClientData(session); |
| |
341 g_return_val_if_fail(pd != NULL, NULL); |
| |
342 |
| |
343 return pd->gc; |
| |
344 } |
| |
345 |
| |
346 |
| |
347 static void write_cb(gpointer data, gint source, GaimInputCondition cond) { |
| |
348 struct mwGaimPluginData *pd = data; |
| |
349 GaimCircBuffer *circ = pd->sock_buf; |
| |
350 gsize avail; |
| |
351 int ret; |
| |
352 |
| |
353 DEBUG_INFO("write_cb\n"); |
| |
354 |
| |
355 g_return_if_fail(circ != NULL); |
| |
356 |
| |
357 avail = gaim_circ_buffer_get_max_read(circ); |
| |
358 if(BUF_LONG < avail) avail = BUF_LONG; |
| |
359 |
| |
360 while(avail) { |
| |
361 ret = write(pd->socket, circ->outptr, avail); |
| |
362 |
| |
363 if(ret <= 0) |
| |
364 break; |
| |
365 |
| |
366 gaim_circ_buffer_mark_read(circ, ret); |
| |
367 avail = gaim_circ_buffer_get_max_read(circ); |
| |
368 if(BUF_LONG < avail) avail = BUF_LONG; |
| |
369 } |
| |
370 |
| |
371 if(! avail) { |
| |
372 gaim_input_remove(pd->outpa); |
| |
373 pd->outpa = 0; |
| |
374 } |
| |
375 } |
| |
376 |
| |
377 |
| |
378 static int mw_session_io_write(struct mwSession *session, |
| |
379 const guchar *buf, gsize len) { |
| |
380 struct mwGaimPluginData *pd; |
| |
381 int ret = 0; |
| |
382 int err = 0; |
| |
383 |
| |
384 pd = mwSession_getClientData(session); |
| |
385 |
| |
386 /* socket was already closed. */ |
| |
387 if(pd->socket == 0) |
| |
388 return 1; |
| |
389 |
| |
390 if(pd->outpa) { |
| |
391 DEBUG_INFO("already pending INPUT_WRITE, buffering\n"); |
| |
392 gaim_circ_buffer_append(pd->sock_buf, buf, len); |
| |
393 return 0; |
| |
394 } |
| |
395 |
| |
396 while(len) { |
| |
397 ret = write(pd->socket, buf, (len > BUF_LEN)? BUF_LEN: len); |
| |
398 |
| |
399 if(ret <= 0) |
| |
400 break; |
| |
401 |
| |
402 len -= ret; |
| |
403 buf += ret; |
| |
404 } |
| |
405 |
| |
406 if(ret <= 0) |
| |
407 err = errno; |
| |
408 |
| |
409 if(err == EAGAIN) { |
| |
410 /* append remainder to circular buffer */ |
| |
411 DEBUG_INFO("EAGAIN\n"); |
| |
412 gaim_circ_buffer_append(pd->sock_buf, buf, len); |
| |
413 pd->outpa = gaim_input_add(pd->socket, GAIM_INPUT_WRITE, write_cb, pd); |
| |
414 |
| |
415 } else if(len > 0) { |
| |
416 DEBUG_ERROR("write returned %i, %i bytes left unwritten\n", ret, len); |
| |
417 gaim_connection_error(pd->gc, _("Connection closed (writing)")); |
| |
418 |
| |
419 #if 0 |
| |
420 close(pd->socket); |
| |
421 pd->socket = 0; |
| |
422 #endif |
| |
423 |
| |
424 return -1; |
| |
425 } |
| |
426 |
| |
427 return 0; |
| |
428 } |
| |
429 |
| |
430 |
| |
431 static void mw_session_io_close(struct mwSession *session) { |
| |
432 struct mwGaimPluginData *pd; |
| |
433 GaimConnection *gc; |
| |
434 |
| |
435 pd = mwSession_getClientData(session); |
| |
436 g_return_if_fail(pd != NULL); |
| |
437 |
| |
438 gc = pd->gc; |
| |
439 |
| |
440 if(pd->outpa) { |
| |
441 gaim_input_remove(pd->outpa); |
| |
442 pd->outpa = 0; |
| |
443 } |
| |
444 |
| |
445 if(pd->socket) { |
| |
446 close(pd->socket); |
| |
447 pd->socket = 0; |
| |
448 } |
| |
449 |
| |
450 if(gc->inpa) { |
| |
451 gaim_input_remove(gc->inpa); |
| |
452 gc->inpa = 0; |
| |
453 } |
| |
454 } |
| |
455 |
| |
456 |
| |
457 static void mw_session_clear(struct mwSession *session) { |
| |
458 ; /* nothing for now */ |
| |
459 } |
| |
460 |
| |
461 |
| |
462 /* ----- aware list ----- */ |
| |
463 |
| |
464 |
| |
465 static void blist_resolve_alias_cb(struct mwServiceResolve *srvc, |
| |
466 guint32 id, guint32 code, GList *results, |
| |
467 gpointer data) { |
| |
468 struct mwResolveResult *result; |
| |
469 struct mwResolveMatch *match; |
| |
470 |
| |
471 g_return_if_fail(results != NULL); |
| |
472 |
| |
473 result = results->data; |
| |
474 g_return_if_fail(result != NULL); |
| |
475 g_return_if_fail(result->matches != NULL); |
| |
476 |
| |
477 match = result->matches->data; |
| |
478 g_return_if_fail(match != NULL); |
| |
479 |
| |
480 gaim_blist_server_alias_buddy(data, match->name); |
| |
481 gaim_blist_node_set_string(data, BUDDY_KEY_NAME, match->name); |
| |
482 } |
| |
483 |
| |
484 |
| |
485 static void mw_aware_list_on_aware(struct mwAwareList *list, |
| |
486 struct mwAwareSnapshot *aware) { |
| |
487 |
| |
488 GaimConnection *gc; |
| |
489 GaimAccount *acct; |
| |
490 |
| |
491 struct mwGaimPluginData *pd; |
| |
492 guint32 idle; |
| |
493 guint stat; |
| |
494 const char *id; |
| |
495 const char *status = MW_STATE_ACTIVE; |
| |
496 |
| |
497 gc = mwAwareList_getClientData(list); |
| |
498 acct = gaim_connection_get_account(gc); |
| |
499 |
| |
500 pd = gc->proto_data; |
| |
501 idle = aware->status.time; |
| |
502 stat = aware->status.status; |
| |
503 id = aware->id.user; |
| |
504 |
| |
505 if(idle) { |
| |
506 guint32 idle_len; /*< how long a client has been idle */ |
| |
507 guint32 ugly_idle_len; /*< how long a broken client has been idle */ |
| |
508 |
| |
509 DEBUG_INFO("%s has idle value 0x%x\n", NSTR(id), idle); |
| |
510 |
| |
511 idle_len = time(NULL) - idle; |
| |
512 ugly_idle_len = ((time(NULL) * 1000) - idle) / 1000; |
| |
513 |
| |
514 /* |
| |
515 what's the deal here? Well, good clients are smart enough to |
| |
516 publish their idle time by using an attribute to indicate that |
| |
517 they went idle at some time UTC, in seconds since epoch. Bad |
| |
518 clients use milliseconds since epoch. So we're going to compute |
| |
519 the idle time for either method, then figure out the lower of |
| |
520 the two and use that. Blame the ST 7.5 development team for |
| |
521 this. |
| |
522 */ |
| |
523 |
| |
524 DEBUG_INFO("idle time: %u, ugly idle time: %u\n", idle_len, ugly_idle_len); |
| |
525 |
| |
526 #if 1 |
| |
527 if(idle_len <= ugly_idle_len) { |
| |
528 ; /* DEBUG_INFO("sane idle value, let's use it\n"); */ |
| |
529 } else { |
| |
530 idle = time(NULL) - ugly_idle_len; |
| |
531 } |
| |
532 |
| |
533 #else |
| |
534 if(idle < 0 || idle > time(NULL)) { |
| |
535 DEBUG_INFO("hiding a messy idle value 0x%x\n", NSTR(id), idle); |
| |
536 idle = -1; |
| |
537 } |
| |
538 #endif |
| |
539 } |
| |
540 |
| |
541 switch(stat) { |
| |
542 case mwStatus_ACTIVE: |
| |
543 status = MW_STATE_ACTIVE; |
| |
544 idle = 0; |
| |
545 break; |
| |
546 |
| |
547 case mwStatus_IDLE: |
| |
548 if(! idle) idle = -1; |
| |
549 break; |
| |
550 |
| |
551 case mwStatus_AWAY: |
| |
552 status = MW_STATE_AWAY; |
| |
553 break; |
| |
554 |
| |
555 case mwStatus_BUSY: |
| |
556 status = MW_STATE_BUSY; |
| |
557 break; |
| |
558 } |
| |
559 |
| |
560 /* NAB group members */ |
| |
561 if(aware->group) { |
| |
562 GaimGroup *group; |
| |
563 GaimBuddy *buddy; |
| |
564 GaimBlistNode *bnode; |
| |
565 |
| |
566 group = g_hash_table_lookup(pd->group_list_map, list); |
| |
567 buddy = gaim_find_buddy_in_group(acct, id, group); |
| |
568 bnode = (GaimBlistNode *) buddy; |
| |
569 |
| |
570 if(! buddy) { |
| |
571 struct mwServiceResolve *srvc; |
| |
572 GList *query; |
| |
573 |
| |
574 buddy = gaim_buddy_new(acct, id, NULL); |
| |
575 gaim_blist_add_buddy(buddy, NULL, group, NULL); |
| |
576 |
| |
577 bnode = (GaimBlistNode *) buddy; |
| |
578 |
| |
579 srvc = pd->srvc_resolve; |
| |
580 query = g_list_append(NULL, (char *) id); |
| |
581 |
| |
582 mwServiceResolve_resolve(srvc, query, mwResolveFlag_USERS, |
| |
583 blist_resolve_alias_cb, buddy, NULL); |
| |
584 g_list_free(query); |
| |
585 } |
| |
586 |
| |
587 gaim_blist_node_set_int(bnode, BUDDY_KEY_TYPE, mwSametimeUser_NORMAL); |
| |
588 } |
| |
589 |
| |
590 if(aware->online) { |
| |
591 gaim_prpl_got_user_status(acct, id, status, NULL); |
| |
592 gaim_prpl_got_user_idle(acct, id, !!idle, (time_t) idle); |
| |
593 |
| |
594 } else { |
| |
595 gaim_prpl_got_user_status(acct, id, MW_STATE_OFFLINE, NULL); |
| |
596 } |
| |
597 } |
| |
598 |
| |
599 |
| |
600 static void mw_aware_list_on_attrib(struct mwAwareList *list, |
| |
601 struct mwAwareIdBlock *id, |
| |
602 struct mwAwareAttribute *attrib) { |
| |
603 |
| |
604 ; /* nothing. We'll get attribute data as we need it */ |
| |
605 } |
| |
606 |
| |
607 |
| |
608 static void mw_aware_list_clear(struct mwAwareList *list) { |
| |
609 ; /* nothing for now */ |
| |
610 } |
| |
611 |
| |
612 |
| |
613 static struct mwAwareListHandler mw_aware_list_handler = { |
| |
614 .on_aware = mw_aware_list_on_aware, |
| |
615 .on_attrib = mw_aware_list_on_attrib, |
| |
616 .clear = mw_aware_list_clear, |
| |
617 }; |
| |
618 |
| |
619 |
| |
620 /** Ensures that an Aware List is associated with the given group, and |
| |
621 returns that list. */ |
| |
622 static struct mwAwareList * |
| |
623 list_ensure(struct mwGaimPluginData *pd, GaimGroup *group) { |
| |
624 |
| |
625 struct mwAwareList *list; |
| |
626 |
| |
627 g_return_val_if_fail(pd != NULL, NULL); |
| |
628 g_return_val_if_fail(group != NULL, NULL); |
| |
629 |
| |
630 list = g_hash_table_lookup(pd->group_list_map, group); |
| |
631 if(! list) { |
| |
632 list = mwAwareList_new(pd->srvc_aware, &mw_aware_list_handler); |
| |
633 mwAwareList_setClientData(list, pd->gc, NULL); |
| |
634 |
| |
635 mwAwareList_watchAttributes(list, |
| |
636 mwAttribute_AV_PREFS_SET, |
| |
637 mwAttribute_MICROPHONE, |
| |
638 mwAttribute_SPEAKERS, |
| |
639 mwAttribute_VIDEO_CAMERA, |
| |
640 mwAttribute_FILE_TRANSFER, |
| |
641 NULL); |
| |
642 |
| |
643 g_hash_table_replace(pd->group_list_map, group, list); |
| |
644 g_hash_table_insert(pd->group_list_map, list, group); |
| |
645 } |
| |
646 |
| |
647 return list; |
| |
648 } |
| |
649 |
| |
650 |
| |
651 static void blist_export(GaimConnection *gc, struct mwSametimeList *stlist) { |
| |
652 /* - find the account for this connection |
| |
653 - iterate through the buddy list |
| |
654 - add each buddy matching this account to the stlist |
| |
655 */ |
| |
656 |
| |
657 GaimAccount *acct; |
| |
658 GaimBuddyList *blist; |
| |
659 GaimBlistNode *gn, *cn, *bn; |
| |
660 GaimGroup *grp; |
| |
661 GaimBuddy *bdy; |
| |
662 |
| |
663 struct mwSametimeGroup *stg = NULL; |
| |
664 struct mwIdBlock idb = { NULL, NULL }; |
| |
665 |
| |
666 acct = gaim_connection_get_account(gc); |
| |
667 g_return_if_fail(acct != NULL); |
| |
668 |
| |
669 blist = gaim_get_blist(); |
| |
670 g_return_if_fail(blist != NULL); |
| |
671 |
| |
672 for(gn = blist->root; gn; gn = gn->next) { |
| |
673 const char *owner; |
| |
674 const char *gname; |
| |
675 enum mwSametimeGroupType gtype; |
| |
676 gboolean gopen; |
| |
677 |
| |
678 if(! GAIM_BLIST_NODE_IS_GROUP(gn)) continue; |
| |
679 grp = (GaimGroup *) gn; |
| |
680 |
| |
681 /* the group's type (normal or dynamic) */ |
| |
682 gtype = gaim_blist_node_get_int(gn, GROUP_KEY_TYPE); |
| |
683 if(! gtype) gtype = mwSametimeGroup_NORMAL; |
| |
684 |
| |
685 /* if it's a normal group with none of our people in it, skip it */ |
| |
686 if(gtype == mwSametimeGroup_NORMAL && !gaim_group_on_account(grp, acct)) |
| |
687 continue; |
| |
688 |
| |
689 /* if the group has an owner and we're not it, skip it */ |
| |
690 owner = gaim_blist_node_get_string(gn, GROUP_KEY_OWNER); |
| |
691 if(owner && strcmp(owner, gaim_account_get_username(acct))) |
| |
692 continue; |
| |
693 |
| |
694 /* the group's actual name may be different from the gaim group's |
| |
695 name. Find whichever is there */ |
| |
696 gname = gaim_blist_node_get_string(gn, GROUP_KEY_NAME); |
| |
697 if(! gname) gname = grp->name; |
| |
698 |
| |
699 /* we save this, but never actually honor it */ |
| |
700 gopen = ! gaim_blist_node_get_bool(gn, GROUP_KEY_COLLAPSED); |
| |
701 |
| |
702 stg = mwSametimeGroup_new(stlist, gtype, gname); |
| |
703 mwSametimeGroup_setAlias(stg, grp->name); |
| |
704 mwSametimeGroup_setOpen(stg, gopen); |
| |
705 |
| |
706 /* don't attempt to put buddies in a dynamic group, it breaks |
| |
707 other clients */ |
| |
708 if(gtype == mwSametimeGroup_DYNAMIC) |
| |
709 continue; |
| |
710 |
| |
711 for(cn = gn->child; cn; cn = cn->next) { |
| |
712 if(! GAIM_BLIST_NODE_IS_CONTACT(cn)) continue; |
| |
713 |
| |
714 for(bn = cn->child; bn; bn = bn->next) { |
| |
715 if(! GAIM_BLIST_NODE_IS_BUDDY(bn)) continue; |
| |
716 if(! GAIM_BLIST_NODE_SHOULD_SAVE(bn)) continue; |
| |
717 |
| |
718 bdy = (GaimBuddy *) bn; |
| |
719 |
| |
720 if(bdy->account == acct) { |
| |
721 struct mwSametimeUser *stu; |
| |
722 enum mwSametimeUserType utype; |
| |
723 |
| |
724 idb.user = bdy->name; |
| |
725 |
| |
726 utype = gaim_blist_node_get_int(bn, BUDDY_KEY_TYPE); |
| |
727 if(! utype) utype = mwSametimeUser_NORMAL; |
| |
728 |
| |
729 stu = mwSametimeUser_new(stg, utype, &idb); |
| |
730 mwSametimeUser_setShortName(stu, bdy->server_alias); |
| |
731 mwSametimeUser_setAlias(stu, bdy->alias); |
| |
732 } |
| |
733 } |
| |
734 } |
| |
735 } |
| |
736 } |
| |
737 |
| |
738 |
| |
739 static void blist_store(struct mwGaimPluginData *pd) { |
| |
740 |
| |
741 struct mwSametimeList *stlist; |
| |
742 struct mwServiceStorage *srvc; |
| |
743 struct mwStorageUnit *unit; |
| |
744 |
| |
745 GaimConnection *gc; |
| |
746 |
| |
747 struct mwPutBuffer *b; |
| |
748 struct mwOpaque *o; |
| |
749 |
| |
750 g_return_if_fail(pd != NULL); |
| |
751 |
| |
752 srvc = pd->srvc_store; |
| |
753 g_return_if_fail(srvc != NULL); |
| |
754 |
| |
755 gc = pd->gc; |
| |
756 |
| |
757 if(BLIST_PREF_IS_LOCAL() || BLIST_PREF_IS_MERGE()) { |
| |
758 DEBUG_INFO("preferences indicate not to save remote blist\n"); |
| |
759 return; |
| |
760 |
| |
761 } else if(MW_SERVICE_IS_DEAD(srvc)) { |
| |
762 DEBUG_INFO("aborting save of blist: storage service is not alive\n"); |
| |
763 return; |
| |
764 |
| |
765 } else if(BLIST_PREF_IS_STORE() || BLIST_PREF_IS_SYNCH()) { |
| |
766 DEBUG_INFO("saving remote blist\n"); |
| |
767 |
| |
768 } else { |
| |
769 g_return_if_reached(); |
| |
770 } |
| |
771 |
| |
772 /* create and export to a list object */ |
| |
773 stlist = mwSametimeList_new(); |
| |
774 blist_export(gc, stlist); |
| |
775 |
| |
776 /* write it to a buffer */ |
| |
777 b = mwPutBuffer_new(); |
| |
778 mwSametimeList_put(b, stlist); |
| |
779 mwSametimeList_free(stlist); |
| |
780 |
| |
781 /* put the buffer contents into a storage unit */ |
| |
782 unit = mwStorageUnit_new(mwStore_AWARE_LIST); |
| |
783 o = mwStorageUnit_asOpaque(unit); |
| |
784 mwPutBuffer_finalize(o, b); |
| |
785 |
| |
786 /* save the storage unit to the service */ |
| |
787 mwServiceStorage_save(srvc, unit, NULL, NULL, NULL); |
| |
788 } |
| |
789 |
| |
790 |
| |
791 static gboolean blist_save_cb(gpointer data) { |
| |
792 struct mwGaimPluginData *pd = data; |
| |
793 |
| |
794 blist_store(pd); |
| |
795 pd->save_event = 0; |
| |
796 return FALSE; |
| |
797 } |
| |
798 |
| |
799 |
| |
800 /** schedules the buddy list to be saved to the server */ |
| |
801 static void blist_schedule(struct mwGaimPluginData *pd) { |
| |
802 if(pd->save_event) return; |
| |
803 |
| |
804 pd->save_event = gaim_timeout_add(BLIST_SAVE_SECONDS * 1000, |
| |
805 blist_save_cb, pd); |
| |
806 } |
| |
807 |
| |
808 |
| |
809 static gboolean buddy_is_external(GaimBuddy *b) { |
| |
810 g_return_val_if_fail(b != NULL, FALSE); |
| |
811 return gaim_str_has_prefix(b->name, "@E "); |
| |
812 } |
| |
813 |
| |
814 |
| |
815 /** Actually add a buddy to the aware service, and schedule the buddy |
| |
816 list to be saved to the server */ |
| |
817 static void buddy_add(struct mwGaimPluginData *pd, |
| |
818 GaimBuddy *buddy) { |
| |
819 |
| |
820 struct mwAwareIdBlock idb = { mwAware_USER, (char *) buddy->name, NULL }; |
| |
821 struct mwAwareList *list; |
| |
822 |
| |
823 GaimGroup *group; |
| |
824 GList *add; |
| |
825 |
| |
826 add = g_list_prepend(NULL, &idb); |
| |
827 |
| |
828 group = gaim_buddy_get_group(buddy); |
| |
829 list = list_ensure(pd, group); |
| |
830 |
| |
831 if(mwAwareList_addAware(list, add)) { |
| |
832 gaim_blist_remove_buddy(buddy); |
| |
833 } |
| |
834 |
| |
835 blist_schedule(pd); |
| |
836 |
| |
837 g_list_free(add); |
| |
838 } |
| |
839 |
| |
840 |
| |
841 /** ensure that a GaimBuddy exists in the group with data |
| |
842 appropriately matching the st user entry from the st list */ |
| |
843 static GaimBuddy *buddy_ensure(GaimConnection *gc, GaimGroup *group, |
| |
844 struct mwSametimeUser *stuser) { |
| |
845 |
| |
846 struct mwGaimPluginData *pd = gc->proto_data; |
| |
847 GaimBuddy *buddy; |
| |
848 GaimAccount *acct = gaim_connection_get_account(gc); |
| |
849 |
| |
850 const char *id = mwSametimeUser_getUser(stuser); |
| |
851 const char *name = mwSametimeUser_getShortName(stuser); |
| |
852 const char *alias = mwSametimeUser_getAlias(stuser); |
| |
853 enum mwSametimeUserType type = mwSametimeUser_getType(stuser); |
| |
854 |
| |
855 g_return_val_if_fail(id != NULL, NULL); |
| |
856 g_return_val_if_fail(strlen(id) > 0, NULL); |
| |
857 |
| |
858 buddy = gaim_find_buddy_in_group(acct, id, group); |
| |
859 if(! buddy) { |
| |
860 buddy = gaim_buddy_new(acct, id, alias); |
| |
861 |
| |
862 gaim_blist_add_buddy(buddy, NULL, group, NULL); |
| |
863 buddy_add(pd, buddy); |
| |
864 } |
| |
865 |
| |
866 gaim_blist_alias_buddy(buddy, alias); |
| |
867 gaim_blist_server_alias_buddy(buddy, name); |
| |
868 gaim_blist_node_set_string((GaimBlistNode *) buddy, BUDDY_KEY_NAME, name); |
| |
869 gaim_blist_node_set_int((GaimBlistNode *) buddy, BUDDY_KEY_TYPE, type); |
| |
870 |
| |
871 return buddy; |
| |
872 } |
| |
873 |
| |
874 |
| |
875 /** add aware watch for a dynamic group */ |
| |
876 static void group_add(struct mwGaimPluginData *pd, |
| |
877 GaimGroup *group) { |
| |
878 |
| |
879 struct mwAwareIdBlock idb = { mwAware_GROUP, NULL, NULL }; |
| |
880 struct mwAwareList *list; |
| |
881 const char *n; |
| |
882 GList *add; |
| |
883 |
| |
884 n = gaim_blist_node_get_string((GaimBlistNode *) group, GROUP_KEY_NAME); |
| |
885 if(! n) n = group->name; |
| |
886 |
| |
887 idb.user = (char *) n; |
| |
888 add = g_list_prepend(NULL, &idb); |
| |
889 |
| |
890 list = list_ensure(pd, group); |
| |
891 mwAwareList_addAware(list, add); |
| |
892 g_list_free(add); |
| |
893 } |
| |
894 |
| |
895 |
| |
896 /** ensure that a GaimGroup exists in the blist with data |
| |
897 appropriately matching the st group entry from the st list */ |
| |
898 static GaimGroup *group_ensure(GaimConnection *gc, |
| |
899 struct mwSametimeGroup *stgroup) { |
| |
900 GaimAccount *acct; |
| |
901 GaimGroup *group = NULL; |
| |
902 GaimBuddyList *blist; |
| |
903 GaimBlistNode *gn; |
| |
904 const char *name, *alias, *owner; |
| |
905 enum mwSametimeGroupType type; |
| |
906 |
| |
907 acct = gaim_connection_get_account(gc); |
| |
908 owner = gaim_account_get_username(acct); |
| |
909 |
| |
910 blist = gaim_get_blist(); |
| |
911 g_return_val_if_fail(blist != NULL, NULL); |
| |
912 |
| |
913 name = mwSametimeGroup_getName(stgroup); |
| |
914 alias = mwSametimeGroup_getAlias(stgroup); |
| |
915 type = mwSametimeGroup_getType(stgroup); |
| |
916 |
| |
917 DEBUG_INFO("attempting to ensure group %s, called %s\n", |
| |
918 NSTR(name), NSTR(alias)); |
| |
919 |
| |
920 /* first attempt at finding the group, by the name key */ |
| |
921 for(gn = blist->root; gn; gn = gn->next) { |
| |
922 const char *n, *o; |
| |
923 if(! GAIM_BLIST_NODE_IS_GROUP(gn)) continue; |
| |
924 n = gaim_blist_node_get_string(gn, GROUP_KEY_NAME); |
| |
925 o = gaim_blist_node_get_string(gn, GROUP_KEY_OWNER); |
| |
926 |
| |
927 DEBUG_INFO("found group named %s, owned by %s\n", NSTR(n), NSTR(o)); |
| |
928 |
| |
929 if(n && !strcmp(n, name)) { |
| |
930 if(!o || !strcmp(o, owner)) { |
| |
931 DEBUG_INFO("that'll work\n"); |
| |
932 group = (GaimGroup *) gn; |
| |
933 break; |
| |
934 } |
| |
935 } |
| |
936 } |
| |
937 |
| |
938 /* try again, by alias */ |
| |
939 if(! group) { |
| |
940 DEBUG_INFO("searching for group by alias %s\n", NSTR(alias)); |
| |
941 group = gaim_find_group(alias); |
| |
942 } |
| |
943 |
| |
944 /* oh well, no such group. Let's create it! */ |
| |
945 if(! group) { |
| |
946 DEBUG_INFO("creating group\n"); |
| |
947 group = gaim_group_new(alias); |
| |
948 gaim_blist_add_group(group, NULL); |
| |
949 } |
| |
950 |
| |
951 gn = (GaimBlistNode *) group; |
| |
952 gaim_blist_node_set_string(gn, GROUP_KEY_NAME, name); |
| |
953 gaim_blist_node_set_int(gn, GROUP_KEY_TYPE, type); |
| |
954 |
| |
955 if(type == mwSametimeGroup_DYNAMIC) { |
| |
956 gaim_blist_node_set_string(gn, GROUP_KEY_OWNER, owner); |
| |
957 group_add(gc->proto_data, group); |
| |
958 } |
| |
959 |
| |
960 return group; |
| |
961 } |
| |
962 |
| |
963 |
| |
964 /** merge the entries from a st list into the gaim blist */ |
| |
965 static void blist_merge(GaimConnection *gc, struct mwSametimeList *stlist) { |
| |
966 struct mwSametimeGroup *stgroup; |
| |
967 struct mwSametimeUser *stuser; |
| |
968 |
| |
969 GaimGroup *group; |
| |
970 GaimBuddy *buddy; |
| |
971 |
| |
972 GList *gl, *gtl, *ul, *utl; |
| |
973 |
| |
974 gl = gtl = mwSametimeList_getGroups(stlist); |
| |
975 for(; gl; gl = gl->next) { |
| |
976 |
| |
977 stgroup = (struct mwSametimeGroup *) gl->data; |
| |
978 group = group_ensure(gc, stgroup); |
| |
979 |
| |
980 ul = utl = mwSametimeGroup_getUsers(stgroup); |
| |
981 for(; ul; ul = ul->next) { |
| |
982 |
| |
983 stuser = (struct mwSametimeUser *) ul->data; |
| |
984 buddy = buddy_ensure(gc, group, stuser); |
| |
985 } |
| |
986 g_list_free(utl); |
| |
987 } |
| |
988 g_list_free(gtl); |
| |
989 } |
| |
990 |
| |
991 |
| |
992 /** remove all buddies on account from group. If del is TRUE and group |
| |
993 is left empty, remove group as well */ |
| |
994 static void group_clear(GaimGroup *group, GaimAccount *acct, gboolean del) { |
| |
995 GaimConnection *gc; |
| |
996 GList *prune = NULL; |
| |
997 GaimBlistNode *gn, *cn, *bn; |
| |
998 |
| |
999 g_return_if_fail(group != NULL); |
| |
1000 |
| |
1001 DEBUG_INFO("clearing members from pruned group %s\n", NSTR(group->name)); |
| |
1002 |
| |
1003 gc = gaim_account_get_connection(acct); |
| |
1004 g_return_if_fail(gc != NULL); |
| |
1005 |
| |
1006 gn = (GaimBlistNode *) group; |
| |
1007 |
| |
1008 for(cn = gn->child; cn; cn = cn->next) { |
| |
1009 if(! GAIM_BLIST_NODE_IS_CONTACT(cn)) continue; |
| |
1010 |
| |
1011 for(bn = cn->child; bn; bn = bn->next) { |
| |
1012 GaimBuddy *gb = (GaimBuddy *) bn; |
| |
1013 |
| |
1014 if(! GAIM_BLIST_NODE_IS_BUDDY(bn)) continue; |
| |
1015 |
| |
1016 if(gb->account == acct) { |
| |
1017 DEBUG_INFO("clearing %s from group\n", NSTR(gb->name)); |
| |
1018 prune = g_list_prepend(prune, gb); |
| |
1019 } |
| |
1020 } |
| |
1021 } |
| |
1022 |
| |
1023 /* quickly unsubscribe from presence for the entire group */ |
| |
1024 gaim_account_remove_group(acct, group); |
| |
1025 |
| |
1026 /* remove blist entries that need to go */ |
| |
1027 while(prune) { |
| |
1028 gaim_blist_remove_buddy(prune->data); |
| |
1029 prune = g_list_delete_link(prune, prune); |
| |
1030 } |
| |
1031 DEBUG_INFO("cleared buddies\n"); |
| |
1032 |
| |
1033 /* optionally remove group from blist */ |
| |
1034 if(del && !gaim_blist_get_group_size(group, TRUE)) { |
| |
1035 DEBUG_INFO("removing empty group\n"); |
| |
1036 gaim_blist_remove_group(group); |
| |
1037 } |
| |
1038 } |
| |
1039 |
| |
1040 |
| |
1041 /** prune out group members that shouldn't be there */ |
| |
1042 static void group_prune(GaimConnection *gc, GaimGroup *group, |
| |
1043 struct mwSametimeGroup *stgroup) { |
| |
1044 |
| |
1045 GaimAccount *acct; |
| |
1046 GaimBlistNode *gn, *cn, *bn; |
| |
1047 |
| |
1048 GHashTable *stusers; |
| |
1049 GList *prune = NULL; |
| |
1050 GList *ul, *utl; |
| |
1051 |
| |
1052 g_return_if_fail(group != NULL); |
| |
1053 |
| |
1054 DEBUG_INFO("pruning membership of group %s\n", NSTR(group->name)); |
| |
1055 |
| |
1056 acct = gaim_connection_get_account(gc); |
| |
1057 g_return_if_fail(acct != NULL); |
| |
1058 |
| |
1059 stusers = g_hash_table_new(g_str_hash, g_str_equal); |
| |
1060 |
| |
1061 /* build a hash table for quick lookup while pruning the group |
| |
1062 contents */ |
| |
1063 utl = mwSametimeGroup_getUsers(stgroup); |
| |
1064 for(ul = utl; ul; ul = ul->next) { |
| |
1065 const char *id = mwSametimeUser_getUser(ul->data); |
| |
1066 g_hash_table_insert(stusers, (char *) id, ul->data); |
| |
1067 DEBUG_INFO("server copy has %s\n", NSTR(id)); |
| |
1068 } |
| |
1069 g_list_free(utl); |
| |
1070 |
| |
1071 gn = (GaimBlistNode *) group; |
| |
1072 |
| |
1073 for(cn = gn->child; cn; cn = cn->next) { |
| |
1074 if(! GAIM_BLIST_NODE_IS_CONTACT(cn)) continue; |
| |
1075 |
| |
1076 for(bn = cn->child; bn; bn = bn->next) { |
| |
1077 GaimBuddy *gb = (GaimBuddy *) bn; |
| |
1078 |
| |
1079 if(! GAIM_BLIST_NODE_IS_BUDDY(bn)) continue; |
| |
1080 |
| |
1081 /* if the account is correct and they're not in our table, mark |
| |
1082 them for pruning */ |
| |
1083 if(gb->account == acct && !g_hash_table_lookup(stusers, gb->name)) { |
| |
1084 DEBUG_INFO("marking %s for pruning\n", NSTR(gb->name)); |
| |
1085 prune = g_list_prepend(prune, gb); |
| |
1086 } |
| |
1087 } |
| |
1088 } |
| |
1089 DEBUG_INFO("done marking\n"); |
| |
1090 |
| |
1091 g_hash_table_destroy(stusers); |
| |
1092 |
| |
1093 if(prune) { |
| |
1094 gaim_account_remove_buddies(acct, prune, NULL); |
| |
1095 while(prune) { |
| |
1096 gaim_blist_remove_buddy(prune->data); |
| |
1097 prune = g_list_delete_link(prune, prune); |
| |
1098 } |
| |
1099 } |
| |
1100 } |
| |
1101 |
| |
1102 |
| |
1103 /** synch the entries from a st list into the gaim blist, removing any |
| |
1104 existing buddies that aren't in the st list */ |
| |
1105 static void blist_sync(GaimConnection *gc, struct mwSametimeList *stlist) { |
| |
1106 |
| |
1107 GaimAccount *acct; |
| |
1108 GaimBuddyList *blist; |
| |
1109 GaimBlistNode *gn; |
| |
1110 |
| |
1111 GHashTable *stgroups; |
| |
1112 GList *g_prune = NULL; |
| |
1113 |
| |
1114 GList *gl, *gtl; |
| |
1115 |
| |
1116 const char *acct_n; |
| |
1117 |
| |
1118 DEBUG_INFO("synchronizing local buddy list from server list\n"); |
| |
1119 |
| |
1120 acct = gaim_connection_get_account(gc); |
| |
1121 g_return_if_fail(acct != NULL); |
| |
1122 |
| |
1123 acct_n = gaim_account_get_username(acct); |
| |
1124 |
| |
1125 blist = gaim_get_blist(); |
| |
1126 g_return_if_fail(blist != NULL); |
| |
1127 |
| |
1128 /* build a hash table for quick lookup while pruning the local |
| |
1129 list, mapping group name to group structure */ |
| |
1130 stgroups = g_hash_table_new(g_str_hash, g_str_equal); |
| |
1131 |
| |
1132 gtl = mwSametimeList_getGroups(stlist); |
| |
1133 for(gl = gtl; gl; gl = gl->next) { |
| |
1134 const char *name = mwSametimeGroup_getName(gl->data); |
| |
1135 g_hash_table_insert(stgroups, (char *) name, gl->data); |
| |
1136 } |
| |
1137 g_list_free(gtl); |
| |
1138 |
| |
1139 /* find all groups which should be pruned from the local list */ |
| |
1140 for(gn = blist->root; gn; gn = gn->next) { |
| |
1141 GaimGroup *grp = (GaimGroup *) gn; |
| |
1142 const char *gname, *owner; |
| |
1143 struct mwSametimeGroup *stgrp; |
| |
1144 |
| |
1145 if(! GAIM_BLIST_NODE_IS_GROUP(gn)) continue; |
| |
1146 |
| |
1147 /* group not belonging to this account */ |
| |
1148 if(! gaim_group_on_account(grp, acct)) |
| |
1149 continue; |
| |
1150 |
| |
1151 /* dynamic group belonging to this account. don't prune contents */ |
| |
1152 owner = gaim_blist_node_get_string(gn, GROUP_KEY_OWNER); |
| |
1153 if(owner && !strcmp(owner, acct_n)) |
| |
1154 continue; |
| |
1155 |
| |
1156 /* we actually are synching by this key as opposed to the group |
| |
1157 title, which can be different things in the st list */ |
| |
1158 gname = gaim_blist_node_get_string(gn, GROUP_KEY_NAME); |
| |
1159 if(! gname) gname = grp->name; |
| |
1160 |
| |
1161 stgrp = g_hash_table_lookup(stgroups, gname); |
| |
1162 if(! stgrp) { |
| |
1163 /* remove the whole group */ |
| |
1164 DEBUG_INFO("marking group %s for pruning\n", grp->name); |
| |
1165 g_prune = g_list_prepend(g_prune, grp); |
| |
1166 |
| |
1167 } else { |
| |
1168 /* synch the group contents */ |
| |
1169 group_prune(gc, grp, stgrp); |
| |
1170 } |
| |
1171 } |
| |
1172 DEBUG_INFO("done marking groups\n"); |
| |
1173 |
| |
1174 /* don't need this anymore */ |
| |
1175 g_hash_table_destroy(stgroups); |
| |
1176 |
| |
1177 /* prune all marked groups */ |
| |
1178 while(g_prune) { |
| |
1179 GaimGroup *grp = g_prune->data; |
| |
1180 GaimBlistNode *gn = (GaimBlistNode *) grp; |
| |
1181 const char *owner; |
| |
1182 gboolean del = TRUE; |
| |
1183 |
| |
1184 owner = gaim_blist_node_get_string(gn, GROUP_KEY_OWNER); |
| |
1185 if(owner && strcmp(owner, acct_n)) { |
| |
1186 /* it's a specialty group belonging to another account with some |
| |
1187 of our members in it, so don't fully delete it */ |
| |
1188 del = FALSE; |
| |
1189 } |
| |
1190 |
| |
1191 group_clear(g_prune->data, acct, del); |
| |
1192 g_prune = g_list_delete_link(g_prune, g_prune); |
| |
1193 } |
| |
1194 |
| |
1195 /* done with the pruning, let's merge in the additions */ |
| |
1196 blist_merge(gc, stlist); |
| |
1197 } |
| |
1198 |
| |
1199 |
| |
1200 /** callback passed to the storage service when it's told to load the |
| |
1201 st list */ |
| |
1202 static void fetch_blist_cb(struct mwServiceStorage *srvc, |
| |
1203 guint32 result, struct mwStorageUnit *item, |
| |
1204 gpointer data) { |
| |
1205 |
| |
1206 struct mwGaimPluginData *pd = data; |
| |
1207 struct mwSametimeList *stlist; |
| |
1208 |
| |
1209 struct mwGetBuffer *b; |
| |
1210 |
| |
1211 g_return_if_fail(result == ERR_SUCCESS); |
| |
1212 |
| |
1213 /* check our preferences for loading */ |
| |
1214 if(BLIST_PREF_IS_LOCAL()) { |
| |
1215 DEBUG_INFO("preferences indicate not to load remote buddy list\n"); |
| |
1216 return; |
| |
1217 } |
| |
1218 |
| |
1219 b = mwGetBuffer_wrap(mwStorageUnit_asOpaque(item)); |
| |
1220 |
| |
1221 stlist = mwSametimeList_new(); |
| |
1222 mwSametimeList_get(b, stlist); |
| |
1223 |
| |
1224 /* merge or synch depending on preferences */ |
| |
1225 if(BLIST_PREF_IS_MERGE() || BLIST_PREF_IS_STORE()) { |
| |
1226 blist_merge(pd->gc, stlist); |
| |
1227 |
| |
1228 } else if(BLIST_PREF_IS_SYNCH()) { |
| |
1229 blist_sync(pd->gc, stlist); |
| |
1230 } |
| |
1231 |
| |
1232 mwSametimeList_free(stlist); |
| |
1233 } |
| |
1234 |
| |
1235 |
| |
1236 /** signal triggered when a conversation is opened in Gaim */ |
| |
1237 static void conversation_created_cb(GaimConversation *g_conv, |
| |
1238 struct mwGaimPluginData *pd) { |
| |
1239 |
| |
1240 /* we need to tell the IM service to negotiate features for the |
| |
1241 conversation right away, otherwise it'll wait until the first |
| |
1242 message is sent before offering NotesBuddy features. Therefore |
| |
1243 whenever Gaim creates a conversation, we'll immediately open the |
| |
1244 channel to the other side and figure out what the target can |
| |
1245 handle. Unfortunately, this makes us vulnerable to Psychic Mode, |
| |
1246 whereas a more lazy negotiation based on the first message |
| |
1247 would not */ |
| |
1248 |
| |
1249 GaimConnection *gc; |
| |
1250 struct mwIdBlock who = { 0, 0 }; |
| |
1251 struct mwConversation *conv; |
| |
1252 |
| |
1253 gc = gaim_conversation_get_gc(g_conv); |
| |
1254 if(pd->gc != gc) |
| |
1255 return; /* not ours */ |
| |
1256 |
| |
1257 if(gaim_conversation_get_type(g_conv) != GAIM_CONV_TYPE_IM) |
| |
1258 return; /* wrong type */ |
| |
1259 |
| |
1260 who.user = (char *) gaim_conversation_get_name(g_conv); |
| |
1261 conv = mwServiceIm_getConversation(pd->srvc_im, &who); |
| |
1262 |
| |
1263 convo_features(conv); |
| |
1264 |
| |
1265 if(mwConversation_isClosed(conv)) |
| |
1266 mwConversation_open(conv); |
| |
1267 } |
| |
1268 |
| |
1269 |
| |
1270 static void blist_menu_nab(GaimBlistNode *node, gpointer data) { |
| |
1271 struct mwGaimPluginData *pd = data; |
| |
1272 GaimConnection *gc; |
| |
1273 |
| |
1274 GaimGroup *group = (GaimGroup *) node; |
| |
1275 |
| |
1276 GString *str; |
| |
1277 char *tmp; |
| |
1278 |
| |
1279 g_return_if_fail(pd != NULL); |
| |
1280 |
| |
1281 gc = pd->gc; |
| |
1282 g_return_if_fail(gc != NULL); |
| |
1283 |
| |
1284 g_return_if_fail(GAIM_BLIST_NODE_IS_GROUP(node)); |
| |
1285 |
| |
1286 str = g_string_new(NULL); |
| |
1287 |
| |
1288 tmp = (char *) gaim_blist_node_get_string(node, GROUP_KEY_NAME); |
| |
1289 |
| |
1290 g_string_append_printf(str, _("<b>Group Title:</b> %s<br>"), group->name); |
| |
1291 g_string_append_printf(str, _("<b>Notes Group ID:</b> %s<br>"), tmp); |
| |
1292 |
| |
1293 tmp = g_strdup_printf(_("Info for Group %s"), group->name); |
| |
1294 |
| |
1295 gaim_notify_formatted(gc, tmp, _("Notes Address Book Information"), |
| |
1296 NULL, str->str, NULL, NULL); |
| |
1297 |
| |
1298 g_free(tmp); |
| |
1299 g_string_free(str, TRUE); |
| |
1300 } |
| |
1301 |
| |
1302 |
| |
1303 /** The normal blist menu prpl function doesn't get called for groups, |
| |
1304 so we use the blist-node-extended-menu signal to trigger this |
| |
1305 handler */ |
| |
1306 static void blist_node_menu_cb(GaimBlistNode *node, |
| |
1307 GList **menu, struct mwGaimPluginData *pd) { |
| |
1308 const char *owner; |
| |
1309 GaimGroup *group; |
| |
1310 GaimAccount *acct; |
| |
1311 GaimMenuAction *act; |
| |
1312 |
| |
1313 /* we only want groups */ |
| |
1314 if(! GAIM_BLIST_NODE_IS_GROUP(node)) return; |
| |
1315 group = (GaimGroup *) node; |
| |
1316 |
| |
1317 acct = gaim_connection_get_account(pd->gc); |
| |
1318 g_return_if_fail(acct != NULL); |
| |
1319 |
| |
1320 /* better make sure we're connected */ |
| |
1321 if(! gaim_account_is_connected(acct)) return; |
| |
1322 |
| |
1323 #if 0 |
| |
1324 /* if there's anyone in the group for this acct, offer to invite |
| |
1325 them all to a conference */ |
| |
1326 if(gaim_group_on_account(group, acct)) { |
| |
1327 act = gaim_menu_action_new(_("Invite Group to Conference..."), |
| |
1328 GAIM_CALLBACK(blist_menu_group_invite), |
| |
1329 pd, NULL); |
| |
1330 *menu = g_list_append(*menu, NULL); |
| |
1331 } |
| |
1332 #endif |
| |
1333 |
| |
1334 /* check if it's a NAB group for this account */ |
| |
1335 owner = gaim_blist_node_get_string(node, GROUP_KEY_OWNER); |
| |
1336 if(owner && !strcmp(owner, gaim_account_get_username(acct))) { |
| |
1337 act = gaim_menu_action_new(_("Get Notes Address Book Info"), |
| |
1338 GAIM_CALLBACK(blist_menu_nab), pd, NULL); |
| |
1339 *menu = g_list_append(*menu, act); |
| |
1340 } |
| |
1341 } |
| |
1342 |
| |
1343 |
| |
1344 /* lifted this from oldstatus, since HEAD doesn't do this at login |
| |
1345 anymore. */ |
| |
1346 static void blist_init(GaimAccount *acct) { |
| |
1347 GaimBlistNode *gnode, *cnode, *bnode; |
| |
1348 GList *add_buds = NULL; |
| |
1349 |
| |
1350 for(gnode = gaim_get_blist()->root; gnode; gnode = gnode->next) { |
| |
1351 if(! GAIM_BLIST_NODE_IS_GROUP(gnode)) continue; |
| |
1352 |
| |
1353 for(cnode = gnode->child; cnode; cnode = cnode->next) { |
| |
1354 if(! GAIM_BLIST_NODE_IS_CONTACT(cnode)) |
| |
1355 continue; |
| |
1356 for(bnode = cnode->child; bnode; bnode = bnode->next) { |
| |
1357 GaimBuddy *b; |
| |
1358 if(!GAIM_BLIST_NODE_IS_BUDDY(bnode)) |
| |
1359 continue; |
| |
1360 |
| |
1361 b = (GaimBuddy *)bnode; |
| |
1362 if(b->account == acct) { |
| |
1363 add_buds = g_list_append(add_buds, b); |
| |
1364 } |
| |
1365 } |
| |
1366 } |
| |
1367 } |
| |
1368 |
| |
1369 if(add_buds) { |
| |
1370 gaim_account_add_buddies(acct, add_buds); |
| |
1371 g_list_free(add_buds); |
| |
1372 } |
| |
1373 } |
| |
1374 |
| |
1375 |
| |
1376 /** Last thing to happen from a started session */ |
| |
1377 static void services_starting(struct mwGaimPluginData *pd) { |
| |
1378 |
| |
1379 GaimConnection *gc; |
| |
1380 GaimAccount *acct; |
| |
1381 struct mwStorageUnit *unit; |
| |
1382 GaimBuddyList *blist; |
| |
1383 GaimBlistNode *l; |
| |
1384 |
| |
1385 gc = pd->gc; |
| |
1386 acct = gaim_connection_get_account(gc); |
| |
1387 |
| |
1388 /* grab the buddy list from the server */ |
| |
1389 unit = mwStorageUnit_new(mwStore_AWARE_LIST); |
| |
1390 mwServiceStorage_load(pd->srvc_store, unit, fetch_blist_cb, pd, NULL); |
| |
1391 |
| |
1392 /* find all the NAB groups and subscribe to them */ |
| |
1393 blist = gaim_get_blist(); |
| |
1394 for(l = blist->root; l; l = l->next) { |
| |
1395 GaimGroup *group = (GaimGroup *) l; |
| |
1396 enum mwSametimeGroupType gt; |
| |
1397 const char *owner; |
| |
1398 |
| |
1399 if(! GAIM_BLIST_NODE_IS_GROUP(l)) continue; |
| |
1400 |
| |
1401 /* if the group is ownerless, or has an owner and we're not it, |
| |
1402 skip it */ |
| |
1403 owner = gaim_blist_node_get_string(l, GROUP_KEY_OWNER); |
| |
1404 if(!owner || strcmp(owner, gaim_account_get_username(acct))) |
| |
1405 continue; |
| |
1406 |
| |
1407 gt = gaim_blist_node_get_int(l, GROUP_KEY_TYPE); |
| |
1408 if(gt == mwSametimeGroup_DYNAMIC) |
| |
1409 group_add(pd, group); |
| |
1410 } |
| |
1411 |
| |
1412 /* set the aware attributes */ |
| |
1413 /* indicate we understand what AV prefs are, but don't support any */ |
| |
1414 mwServiceAware_setAttributeBoolean(pd->srvc_aware, |
| |
1415 mwAttribute_AV_PREFS_SET, TRUE); |
| |
1416 mwServiceAware_unsetAttribute(pd->srvc_aware, mwAttribute_MICROPHONE); |
| |
1417 mwServiceAware_unsetAttribute(pd->srvc_aware, mwAttribute_SPEAKERS); |
| |
1418 mwServiceAware_unsetAttribute(pd->srvc_aware, mwAttribute_VIDEO_CAMERA); |
| |
1419 |
| |
1420 /* ... but we can do file transfers! */ |
| |
1421 mwServiceAware_setAttributeBoolean(pd->srvc_aware, |
| |
1422 mwAttribute_FILE_TRANSFER, TRUE); |
| |
1423 |
| |
1424 blist_init(acct); |
| |
1425 } |
| |
1426 |
| |
1427 |
| |
1428 static void session_loginRedirect(struct mwSession *session, |
| |
1429 const char *host) { |
| |
1430 struct mwGaimPluginData *pd; |
| |
1431 GaimConnection *gc; |
| |
1432 GaimAccount *account; |
| |
1433 guint port; |
| |
1434 const char *current_host; |
| |
1435 |
| |
1436 pd = mwSession_getClientData(session); |
| |
1437 gc = pd->gc; |
| |
1438 account = gaim_connection_get_account(gc); |
| |
1439 port = gaim_account_get_int(account, MW_KEY_PORT, MW_PLUGIN_DEFAULT_PORT); |
| |
1440 current_host = gaim_account_get_string(account, MW_KEY_HOST, |
| |
1441 MW_PLUGIN_DEFAULT_HOST); |
| |
1442 |
| |
1443 if(gaim_account_get_bool(account, MW_KEY_FORCE, FALSE) || |
| |
1444 (! strcmp(current_host, host)) || |
| |
1445 (gaim_proxy_connect(NULL, account, host, port, connect_cb, pd) == NULL)) { |
| |
1446 |
| |
1447 /* if we're configured to force logins, or if we're being |
| |
1448 redirected to the already configured host, or if we couldn't |
| |
1449 connect to the new host, we'll force the login instead */ |
| |
1450 |
| |
1451 mwSession_forceLogin(session); |
| |
1452 } |
| |
1453 } |
| |
1454 |
| |
1455 |
| |
1456 static void mw_prpl_set_status(GaimAccount *acct, GaimStatus *status); |
| |
1457 |
| |
1458 |
| |
1459 /** called from mw_session_stateChange when the session's state is |
| |
1460 mwSession_STARTED. Any finalizing of start-up stuff should go |
| |
1461 here */ |
| |
1462 static void session_started(struct mwGaimPluginData *pd) { |
| |
1463 GaimStatus *status; |
| |
1464 GaimAccount *acct; |
| |
1465 |
| |
1466 /* set out initial status */ |
| |
1467 acct = gaim_connection_get_account(pd->gc); |
| |
1468 status = gaim_account_get_active_status(acct); |
| |
1469 mw_prpl_set_status(acct, status); |
| |
1470 |
| |
1471 /* start watching for new conversations */ |
| |
1472 gaim_signal_connect(gaim_conversations_get_handle(), |
| |
1473 "conversation-created", pd, |
| |
1474 GAIM_CALLBACK(conversation_created_cb), pd); |
| |
1475 |
| |
1476 /* watch for group extended menu items */ |
| |
1477 gaim_signal_connect(gaim_blist_get_handle(), |
| |
1478 "blist-node-extended-menu", pd, |
| |
1479 GAIM_CALLBACK(blist_node_menu_cb), pd); |
| |
1480 |
| |
1481 /* use our services to do neat things */ |
| |
1482 services_starting(pd); |
| |
1483 } |
| |
1484 |
| |
1485 |
| |
1486 static void session_stopping(struct mwGaimPluginData *pd) { |
| |
1487 /* stop watching the signals from session_started */ |
| |
1488 gaim_signals_disconnect_by_handle(pd); |
| |
1489 } |
| |
1490 |
| |
1491 |
| |
1492 static void mw_session_stateChange(struct mwSession *session, |
| |
1493 enum mwSessionState state, |
| |
1494 gpointer info) { |
| |
1495 struct mwGaimPluginData *pd; |
| |
1496 GaimConnection *gc; |
| |
1497 const char *msg = NULL; |
| |
1498 |
| |
1499 pd = mwSession_getClientData(session); |
| |
1500 gc = pd->gc; |
| |
1501 |
| |
1502 switch(state) { |
| |
1503 case mwSession_STARTING: |
| |
1504 msg = _("Sending Handshake"); |
| |
1505 gaim_connection_update_progress(gc, msg, 2, MW_CONNECT_STEPS); |
| |
1506 break; |
| |
1507 |
| |
1508 case mwSession_HANDSHAKE: |
| |
1509 msg = _("Waiting for Handshake Acknowledgement"); |
| |
1510 gaim_connection_update_progress(gc, msg, 3, MW_CONNECT_STEPS); |
| |
1511 break; |
| |
1512 |
| |
1513 case mwSession_HANDSHAKE_ACK: |
| |
1514 msg = _("Handshake Acknowledged, Sending Login"); |
| |
1515 gaim_connection_update_progress(gc, msg, 4, MW_CONNECT_STEPS); |
| |
1516 break; |
| |
1517 |
| |
1518 case mwSession_LOGIN: |
| |
1519 msg = _("Waiting for Login Acknowledgement"); |
| |
1520 gaim_connection_update_progress(gc, msg, 5, MW_CONNECT_STEPS); |
| |
1521 break; |
| |
1522 |
| |
1523 case mwSession_LOGIN_REDIR: |
| |
1524 msg = _("Login Redirected"); |
| |
1525 gaim_connection_update_progress(gc, msg, 6, MW_CONNECT_STEPS); |
| |
1526 session_loginRedirect(session, info); |
| |
1527 break; |
| |
1528 |
| |
1529 case mwSession_LOGIN_CONT: |
| |
1530 msg = _("Forcing Login"); |
| |
1531 gaim_connection_update_progress(gc, msg, 7, MW_CONNECT_STEPS); |
| |
1532 |
| |
1533 case mwSession_LOGIN_ACK: |
| |
1534 msg = _("Login Acknowledged"); |
| |
1535 gaim_connection_update_progress(gc, msg, 8, MW_CONNECT_STEPS); |
| |
1536 break; |
| |
1537 |
| |
1538 case mwSession_STARTED: |
| |
1539 msg = _("Starting Services"); |
| |
1540 gaim_connection_update_progress(gc, msg, 9, MW_CONNECT_STEPS); |
| |
1541 |
| |
1542 session_started(pd); |
| |
1543 |
| |
1544 msg = _("Connected"); |
| |
1545 gaim_connection_update_progress(gc, msg, 10, MW_CONNECT_STEPS); |
| |
1546 gaim_connection_set_state(gc, GAIM_CONNECTED); |
| |
1547 break; |
| |
1548 |
| |
1549 case mwSession_STOPPING: |
| |
1550 |
| |
1551 session_stopping(pd); |
| |
1552 |
| |
1553 if(GPOINTER_TO_UINT(info) & ERR_FAILURE) { |
| |
1554 char *err = mwError(GPOINTER_TO_UINT(info)); |
| |
1555 gaim_connection_error(gc, err); |
| |
1556 g_free(err); |
| |
1557 } |
| |
1558 break; |
| |
1559 |
| |
1560 case mwSession_STOPPED: |
| |
1561 break; |
| |
1562 |
| |
1563 case mwSession_UNKNOWN: |
| |
1564 default: |
| |
1565 DEBUG_WARN("session in unknown state\n"); |
| |
1566 } |
| |
1567 } |
| |
1568 |
| |
1569 |
| |
1570 static void mw_session_setPrivacyInfo(struct mwSession *session) { |
| |
1571 struct mwGaimPluginData *pd; |
| |
1572 GaimConnection *gc; |
| |
1573 GaimAccount *acct; |
| |
1574 struct mwPrivacyInfo *privacy; |
| |
1575 GSList *l, **ll; |
| |
1576 guint count; |
| |
1577 |
| |
1578 DEBUG_INFO("privacy information set from server\n"); |
| |
1579 |
| |
1580 g_return_if_fail(session != NULL); |
| |
1581 |
| |
1582 pd = mwSession_getClientData(session); |
| |
1583 g_return_if_fail(pd != NULL); |
| |
1584 |
| |
1585 gc = pd->gc; |
| |
1586 g_return_if_fail(gc != NULL); |
| |
1587 |
| |
1588 acct = gaim_connection_get_account(gc); |
| |
1589 g_return_if_fail(acct != NULL); |
| |
1590 |
| |
1591 privacy = mwSession_getPrivacyInfo(session); |
| |
1592 count = privacy->count; |
| |
1593 |
| |
1594 ll = (privacy->deny)? &acct->deny: &acct->permit; |
| |
1595 for(l = *ll; l; l = l->next) g_free(l->data); |
| |
1596 g_slist_free(*ll); |
| |
1597 l = *ll = NULL; |
| |
1598 |
| |
1599 while(count--) { |
| |
1600 struct mwUserItem *u = privacy->users + count; |
| |
1601 l = g_slist_prepend(l, g_strdup(u->id)); |
| |
1602 } |
| |
1603 *ll = l; |
| |
1604 } |
| |
1605 |
| |
1606 |
| |
1607 static void mw_session_setUserStatus(struct mwSession *session) { |
| |
1608 struct mwGaimPluginData *pd; |
| |
1609 GaimConnection *gc; |
| |
1610 struct mwAwareIdBlock idb = { mwAware_USER, NULL, NULL }; |
| |
1611 struct mwUserStatus *stat; |
| |
1612 |
| |
1613 g_return_if_fail(session != NULL); |
| |
1614 |
| |
1615 pd = mwSession_getClientData(session); |
| |
1616 g_return_if_fail(pd != NULL); |
| |
1617 |
| |
1618 gc = pd->gc; |
| |
1619 g_return_if_fail(gc != NULL); |
| |
1620 |
| |
1621 idb.user = mwSession_getProperty(session, mwSession_AUTH_USER_ID); |
| |
1622 stat = mwSession_getUserStatus(session); |
| |
1623 |
| |
1624 /* trigger an update of our own status if we're in the buddy list */ |
| |
1625 mwServiceAware_setStatus(pd->srvc_aware, &idb, stat); |
| |
1626 } |
| |
1627 |
| |
1628 |
| |
1629 static void mw_session_admin(struct mwSession *session, |
| |
1630 const char *text) { |
| |
1631 GaimConnection *gc; |
| |
1632 GaimAccount *acct; |
| |
1633 const char *host; |
| |
1634 const char *msg; |
| |
1635 char *prim; |
| |
1636 |
| |
1637 gc = session_to_gc(session); |
| |
1638 g_return_if_fail(gc != NULL); |
| |
1639 |
| |
1640 acct = gaim_connection_get_account(gc); |
| |
1641 g_return_if_fail(acct != NULL); |
| |
1642 |
| |
1643 host = gaim_account_get_string(acct, MW_KEY_HOST, NULL); |
| |
1644 |
| |
1645 msg = _("A Sametime administrator has issued the following announcement" |
| |
1646 " on server %s"); |
| |
1647 prim = g_strdup_printf(msg, NSTR(host)); |
| |
1648 |
| |
1649 gaim_notify_message(gc, GAIM_NOTIFY_MSG_INFO, |
| |
1650 _("Sametime Administrator Announcement"), |
| |
1651 prim, text, NULL, NULL); |
| |
1652 |
| |
1653 g_free(prim); |
| |
1654 } |
| |
1655 |
| |
1656 |
| |
1657 /** called from read_cb, attempts to read available data from sock and |
| |
1658 pass it to the session, passing back the return code from the read |
| |
1659 call for handling in read_cb */ |
| |
1660 static int read_recv(struct mwSession *session, int sock) { |
| |
1661 guchar buf[BUF_LEN]; |
| |
1662 int len; |
| |
1663 |
| |
1664 len = read(sock, buf, BUF_LEN); |
| |
1665 if(len > 0) mwSession_recv(session, buf, len); |
| |
1666 |
| |
1667 return len; |
| |
1668 } |
| |
1669 |
| |
1670 |
| |
1671 /** callback triggered from gaim_input_add, watches the socked for |
| |
1672 available data to be processed by the session */ |
| |
1673 static void read_cb(gpointer data, gint source, GaimInputCondition cond) { |
| |
1674 struct mwGaimPluginData *pd = data; |
| |
1675 int ret = 0, err = 0; |
| |
1676 |
| |
1677 g_return_if_fail(pd != NULL); |
| |
1678 |
| |
1679 ret = read_recv(pd->session, pd->socket); |
| |
1680 |
| |
1681 /* normal operation ends here */ |
| |
1682 if(ret > 0) return; |
| |
1683 |
| |
1684 /* fetch the global error value */ |
| |
1685 err = errno; |
| |
1686 |
| |
1687 /* read problem occurred if we're here, so we'll need to take care of |
| |
1688 it and clean up internal state */ |
| |
1689 |
| |
1690 if(pd->socket) { |
| |
1691 close(pd->socket); |
| |
1692 pd->socket = 0; |
| |
1693 } |
| |
1694 |
| |
1695 if(pd->gc->inpa) { |
| |
1696 gaim_input_remove(pd->gc->inpa); |
| |
1697 pd->gc->inpa = 0; |
| |
1698 } |
| |
1699 |
| |
1700 if(! ret) { |
| |
1701 DEBUG_INFO("connection reset\n"); |
| |
1702 gaim_connection_error(pd->gc, _("Connection reset")); |
| |
1703 |
| |
1704 } else if(ret < 0) { |
| |
1705 char *msg = strerror(err); |
| |
1706 |
| |
1707 DEBUG_INFO("error in read callback: %s\n", msg); |
| |
1708 |
| |
1709 msg = g_strdup_printf(_("Error reading from socket: %s"), msg); |
| |
1710 gaim_connection_error(pd->gc, msg); |
| |
1711 g_free(msg); |
| |
1712 } |
| |
1713 } |
| |
1714 |
| |
1715 |
| |
1716 /** Callback passed to gaim_proxy_connect when an account is logged |
| |
1717 in, and if the session logging in receives a redirect message */ |
| |
1718 static void connect_cb(gpointer data, gint source, const gchar *error_message) { |
| |
1719 |
| |
1720 struct mwGaimPluginData *pd = data; |
| |
1721 GaimConnection *gc = pd->gc; |
| |
1722 |
| |
1723 if(source < 0) { |
| |
1724 /* connection failed */ |
| |
1725 |
| |
1726 if(pd->socket) { |
| |
1727 /* this is a redirect connect, force login on existing socket */ |
| |
1728 mwSession_forceLogin(pd->session); |
| |
1729 |
| |
1730 } else { |
| |
1731 /* this is a regular connect, error out */ |
| |
1732 gaim_connection_error(pd->gc, _("Unable to connect to host")); |
| |
1733 } |
| |
1734 |
| |
1735 return; |
| |
1736 } |
| |
1737 |
| |
1738 if(pd->socket) { |
| |
1739 /* stop any existing login attempt */ |
| |
1740 mwSession_stop(pd->session, ERR_SUCCESS); |
| |
1741 } |
| |
1742 |
| |
1743 pd->socket = source; |
| |
1744 gc->inpa = gaim_input_add(source, GAIM_INPUT_READ, |
| |
1745 read_cb, pd); |
| |
1746 |
| |
1747 mwSession_start(pd->session); |
| |
1748 } |
| |
1749 |
| |
1750 |
| |
1751 static void mw_session_announce(struct mwSession *s, |
| |
1752 struct mwLoginInfo *from, |
| |
1753 gboolean may_reply, |
| |
1754 const char *text) { |
| |
1755 struct mwGaimPluginData *pd; |
| |
1756 GaimAccount *acct; |
| |
1757 GaimConversation *conv; |
| |
1758 GaimBuddy *buddy; |
| |
1759 char *who = from->user_id; |
| |
1760 char *msg; |
| |
1761 |
| |
1762 pd = mwSession_getClientData(s); |
| |
1763 acct = gaim_connection_get_account(pd->gc); |
| |
1764 conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, who, acct); |
| |
1765 if(! conv) conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, acct, who); |
| |
1766 |
| |
1767 buddy = gaim_find_buddy(acct, who); |
| |
1768 if(buddy) who = (char *) gaim_buddy_get_contact_alias(buddy); |
| |
1769 |
| |
1770 who = g_strdup_printf(_("Announcement from %s"), who); |
| |
1771 msg = gaim_markup_linkify(text); |
| |
1772 |
| |
1773 gaim_conversation_write(conv, who, msg, GAIM_MESSAGE_RECV, time(NULL)); |
| |
1774 g_free(who); |
| |
1775 g_free(msg); |
| |
1776 } |
| |
1777 |
| |
1778 |
| |
1779 static struct mwSessionHandler mw_session_handler = { |
| |
1780 .io_write = mw_session_io_write, |
| |
1781 .io_close = mw_session_io_close, |
| |
1782 .clear = mw_session_clear, |
| |
1783 .on_stateChange = mw_session_stateChange, |
| |
1784 .on_setPrivacyInfo = mw_session_setPrivacyInfo, |
| |
1785 .on_setUserStatus = mw_session_setUserStatus, |
| |
1786 .on_admin = mw_session_admin, |
| |
1787 .on_announce = mw_session_announce, |
| |
1788 }; |
| |
1789 |
| |
1790 |
| |
1791 static void mw_aware_on_attrib(struct mwServiceAware *srvc, |
| |
1792 struct mwAwareAttribute *attrib) { |
| |
1793 |
| |
1794 ; /** @todo handle server attributes. There may be some stuff we |
| |
1795 actually want to look for, but I'm not aware of anything right |
| |
1796 now.*/ |
| |
1797 } |
| |
1798 |
| |
1799 |
| |
1800 static void mw_aware_clear(struct mwServiceAware *srvc) { |
| |
1801 ; /* nothing for now */ |
| |
1802 } |
| |
1803 |
| |
1804 |
| |
1805 static struct mwAwareHandler mw_aware_handler = { |
| |
1806 .on_attrib = mw_aware_on_attrib, |
| |
1807 .clear = mw_aware_clear, |
| |
1808 }; |
| |
1809 |
| |
1810 |
| |
1811 static struct mwServiceAware *mw_srvc_aware_new(struct mwSession *s) { |
| |
1812 struct mwServiceAware *srvc; |
| |
1813 srvc = mwServiceAware_new(s, &mw_aware_handler); |
| |
1814 return srvc; |
| |
1815 }; |
| |
1816 |
| |
1817 |
| |
1818 static void mw_conf_invited(struct mwConference *conf, |
| |
1819 struct mwLoginInfo *inviter, |
| |
1820 const char *invitation) { |
| |
1821 |
| |
1822 struct mwServiceConference *srvc; |
| |
1823 struct mwSession *session; |
| |
1824 struct mwGaimPluginData *pd; |
| |
1825 GaimConnection *gc; |
| |
1826 |
| |
1827 char *c_inviter, *c_name, *c_topic, *c_invitation; |
| |
1828 GHashTable *ht; |
| |
1829 |
| |
1830 srvc = mwConference_getService(conf); |
| |
1831 session = mwService_getSession(MW_SERVICE(srvc)); |
| |
1832 pd = mwSession_getClientData(session); |
| |
1833 gc = pd->gc; |
| |
1834 |
| |
1835 ht = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free); |
| |
1836 |
| |
1837 c_inviter = g_strdup(inviter->user_id); |
| |
1838 g_hash_table_insert(ht, CHAT_KEY_CREATOR, c_inviter); |
| |
1839 |
| |
1840 c_name = g_strdup(mwConference_getName(conf)); |
| |
1841 g_hash_table_insert(ht, CHAT_KEY_NAME, c_name); |
| |
1842 |
| |
1843 c_topic = g_strdup(mwConference_getTitle(conf)); |
| |
1844 g_hash_table_insert(ht, CHAT_KEY_TOPIC, c_topic); |
| |
1845 |
| |
1846 c_invitation = g_strdup(invitation); |
| |
1847 g_hash_table_insert(ht, CHAT_KEY_INVITE, c_invitation); |
| |
1848 |
| |
1849 DEBUG_INFO("received invitation from '%s' to join ('%s','%s'): '%s'\n", |
| |
1850 NSTR(c_inviter), NSTR(c_name), |
| |
1851 NSTR(c_topic), NSTR(c_invitation)); |
| |
1852 |
| |
1853 if(! c_topic) c_topic = "(no title)"; |
| |
1854 if(! c_invitation) c_invitation = "(no message)"; |
| |
1855 serv_got_chat_invite(gc, c_topic, c_inviter, c_invitation, ht); |
| |
1856 } |
| |
1857 |
| |
1858 |
| |
1859 /* The following mess helps us relate a mwConference to a GaimConvChat |
| |
1860 in the various forms by which either may be indicated */ |
| |
1861 |
| |
1862 #define CONF_TO_ID(conf) (GPOINTER_TO_INT(conf)) |
| |
1863 #define ID_TO_CONF(pd, id) (conf_find_by_id((pd), (id))) |
| |
1864 |
| |
1865 #define CHAT_TO_ID(chat) (gaim_conv_chat_get_id(chat)) |
| |
1866 #define ID_TO_CHAT(id) (gaim_find_chat(id)) |
| |
1867 |
| |
1868 #define CHAT_TO_CONF(pd, chat) (ID_TO_CONF((pd), CHAT_TO_ID(chat))) |
| |
1869 #define CONF_TO_CHAT(conf) (ID_TO_CHAT(CONF_TO_ID(conf))) |
| |
1870 |
| |
1871 |
| |
1872 static struct mwConference * |
| |
1873 conf_find_by_id(struct mwGaimPluginData *pd, int id) { |
| |
1874 |
| |
1875 struct mwServiceConference *srvc = pd->srvc_conf; |
| |
1876 struct mwConference *conf = NULL; |
| |
1877 GList *l, *ll; |
| |
1878 |
| |
1879 ll = mwServiceConference_getConferences(srvc); |
| |
1880 for(l = ll; l; l = l->next) { |
| |
1881 struct mwConference *c = l->data; |
| |
1882 GaimConvChat *h = mwConference_getClientData(c); |
| |
1883 |
| |
1884 if(CHAT_TO_ID(h) == id) { |
| |
1885 conf = c; |
| |
1886 break; |
| |
1887 } |
| |
1888 } |
| |
1889 g_list_free(ll); |
| |
1890 |
| |
1891 return conf; |
| |
1892 } |
| |
1893 |
| |
1894 |
| |
1895 static void mw_conf_opened(struct mwConference *conf, GList *members) { |
| |
1896 struct mwServiceConference *srvc; |
| |
1897 struct mwSession *session; |
| |
1898 struct mwGaimPluginData *pd; |
| |
1899 GaimConnection *gc; |
| |
1900 GaimConversation *g_conf; |
| |
1901 |
| |
1902 const char *n = mwConference_getName(conf); |
| |
1903 const char *t = mwConference_getTitle(conf); |
| |
1904 |
| |
1905 DEBUG_INFO("conf %s opened, %u initial members\n", |
| |
1906 NSTR(n), g_list_length(members)); |
| |
1907 |
| |
1908 srvc = mwConference_getService(conf); |
| |
1909 session = mwService_getSession(MW_SERVICE(srvc)); |
| |
1910 pd = mwSession_getClientData(session); |
| |
1911 gc = pd->gc; |
| |
1912 |
| |
1913 if(! t) t = "(no title)"; |
| |
1914 g_conf = serv_got_joined_chat(gc, CONF_TO_ID(conf), t); |
| |
1915 |
| |
1916 mwConference_setClientData(conf, GAIM_CONV_CHAT(g_conf), NULL); |
| |
1917 |
| |
1918 for(; members; members = members->next) { |
| |
1919 struct mwLoginInfo *peer = members->data; |
| |
1920 gaim_conv_chat_add_user(GAIM_CONV_CHAT(g_conf), peer->user_id, |
| |
1921 NULL, GAIM_CBFLAGS_NONE, FALSE); |
| |
1922 } |
| |
1923 } |
| |
1924 |
| |
1925 |
| |
1926 static void mw_conf_closed(struct mwConference *conf, guint32 reason) { |
| |
1927 struct mwServiceConference *srvc; |
| |
1928 struct mwSession *session; |
| |
1929 struct mwGaimPluginData *pd; |
| |
1930 GaimConnection *gc; |
| |
1931 |
| |
1932 const char *n = mwConference_getName(conf); |
| |
1933 char *msg = mwError(reason); |
| |
1934 |
| |
1935 DEBUG_INFO("conf %s closed, 0x%08x\n", NSTR(n), reason); |
| |
1936 |
| |
1937 srvc = mwConference_getService(conf); |
| |
1938 session = mwService_getSession(MW_SERVICE(srvc)); |
| |
1939 pd = mwSession_getClientData(session); |
| |
1940 gc = pd->gc; |
| |
1941 |
| |
1942 serv_got_chat_left(gc, CONF_TO_ID(conf)); |
| |
1943 |
| |
1944 gaim_notify_error(gc, _("Conference Closed"), NULL, msg); |
| |
1945 g_free(msg); |
| |
1946 } |
| |
1947 |
| |
1948 |
| |
1949 static void mw_conf_peer_joined(struct mwConference *conf, |
| |
1950 struct mwLoginInfo *peer) { |
| |
1951 |
| |
1952 struct mwServiceConference *srvc; |
| |
1953 struct mwSession *session; |
| |
1954 struct mwGaimPluginData *pd; |
| |
1955 GaimConnection *gc; |
| |
1956 GaimConvChat *g_conf; |
| |
1957 |
| |
1958 const char *n = mwConference_getName(conf); |
| |
1959 |
| |
1960 DEBUG_INFO("%s joined conf %s\n", NSTR(peer->user_id), NSTR(n)); |
| |
1961 |
| |
1962 srvc = mwConference_getService(conf); |
| |
1963 session = mwService_getSession(MW_SERVICE(srvc)); |
| |
1964 pd = mwSession_getClientData(session); |
| |
1965 gc = pd->gc; |
| |
1966 |
| |
1967 g_conf = mwConference_getClientData(conf); |
| |
1968 g_return_if_fail(g_conf != NULL); |
| |
1969 |
| |
1970 gaim_conv_chat_add_user(g_conf, peer->user_id, |
| |
1971 NULL, GAIM_CBFLAGS_NONE, TRUE); |
| |
1972 } |
| |
1973 |
| |
1974 |
| |
1975 static void mw_conf_peer_parted(struct mwConference *conf, |
| |
1976 struct mwLoginInfo *peer) { |
| |
1977 |
| |
1978 struct mwServiceConference *srvc; |
| |
1979 struct mwSession *session; |
| |
1980 struct mwGaimPluginData *pd; |
| |
1981 GaimConnection *gc; |
| |
1982 GaimConvChat *g_conf; |
| |
1983 |
| |
1984 const char *n = mwConference_getName(conf); |
| |
1985 |
| |
1986 DEBUG_INFO("%s left conf %s\n", NSTR(peer->user_id), NSTR(n)); |
| |
1987 |
| |
1988 srvc = mwConference_getService(conf); |
| |
1989 session = mwService_getSession(MW_SERVICE(srvc)); |
| |
1990 pd = mwSession_getClientData(session); |
| |
1991 gc = pd->gc; |
| |
1992 |
| |
1993 g_conf = mwConference_getClientData(conf); |
| |
1994 g_return_if_fail(g_conf != NULL); |
| |
1995 |
| |
1996 gaim_conv_chat_remove_user(g_conf, peer->user_id, NULL); |
| |
1997 } |
| |
1998 |
| |
1999 |
| |
2000 static void mw_conf_text(struct mwConference *conf, |
| |
2001 struct mwLoginInfo *who, const char *text) { |
| |
2002 |
| |
2003 struct mwServiceConference *srvc; |
| |
2004 struct mwSession *session; |
| |
2005 struct mwGaimPluginData *pd; |
| |
2006 GaimConnection *gc; |
| |
2007 char *esc; |
| |
2008 |
| |
2009 if(! text) return; |
| |
2010 |
| |
2011 srvc = mwConference_getService(conf); |
| |
2012 session = mwService_getSession(MW_SERVICE(srvc)); |
| |
2013 pd = mwSession_getClientData(session); |
| |
2014 gc = pd->gc; |
| |
2015 |
| |
2016 esc = g_markup_escape_text(text, -1); |
| |
2017 serv_got_chat_in(gc, CONF_TO_ID(conf), who->user_id, 0, esc, time(NULL)); |
| |
2018 g_free(esc); |
| |
2019 } |
| |
2020 |
| |
2021 |
| |
2022 static void mw_conf_typing(struct mwConference *conf, |
| |
2023 struct mwLoginInfo *who, gboolean typing) { |
| |
2024 |
| |
2025 /* gaim really has no good way to expose this to the user. */ |
| |
2026 |
| |
2027 const char *n = mwConference_getName(conf); |
| |
2028 const char *w = who->user_id; |
| |
2029 |
| |
2030 if(typing) { |
| |
2031 DEBUG_INFO("%s in conf %s: <typing>\n", NSTR(w), NSTR(n)); |
| |
2032 |
| |
2033 } else { |
| |
2034 DEBUG_INFO("%s in conf %s: <stopped typing>\n", NSTR(w), NSTR(n)); |
| |
2035 } |
| |
2036 } |
| |
2037 |
| |
2038 |
| |
2039 static void mw_conf_clear(struct mwServiceConference *srvc) { |
| |
2040 ; |
| |
2041 } |
| |
2042 |
| |
2043 |
| |
2044 static struct mwConferenceHandler mw_conference_handler = { |
| |
2045 .on_invited = mw_conf_invited, |
| |
2046 .conf_opened = mw_conf_opened, |
| |
2047 .conf_closed = mw_conf_closed, |
| |
2048 .on_peer_joined = mw_conf_peer_joined, |
| |
2049 .on_peer_parted = mw_conf_peer_parted, |
| |
2050 .on_text = mw_conf_text, |
| |
2051 .on_typing = mw_conf_typing, |
| |
2052 .clear = mw_conf_clear, |
| |
2053 }; |
| |
2054 |
| |
2055 |
| |
2056 static struct mwServiceConference *mw_srvc_conf_new(struct mwSession *s) { |
| |
2057 struct mwServiceConference *srvc; |
| |
2058 srvc = mwServiceConference_new(s, &mw_conference_handler); |
| |
2059 return srvc; |
| |
2060 } |
| |
2061 |
| |
2062 |
| |
2063 /** size of an outgoing file transfer chunk */ |
| |
2064 #define MW_FT_LEN (BUF_LONG * 2) |
| |
2065 |
| |
2066 |
| |
2067 static void ft_incoming_cancel(GaimXfer *xfer) { |
| |
2068 /* incoming transfer rejected or canceled in-progress */ |
| |
2069 struct mwFileTransfer *ft = xfer->data; |
| |
2070 if(ft) mwFileTransfer_reject(ft); |
| |
2071 } |
| |
2072 |
| |
2073 |
| |
2074 static void ft_incoming_init(GaimXfer *xfer) { |
| |
2075 /* incoming transfer accepted */ |
| |
2076 |
| |
2077 /* - accept the mwFileTransfer |
| |
2078 - open/create the local FILE "wb" |
| |
2079 - stick the FILE's fp in xfer->dest_fp |
| |
2080 */ |
| |
2081 |
| |
2082 struct mwFileTransfer *ft; |
| |
2083 FILE *fp; |
| |
2084 |
| |
2085 ft = xfer->data; |
| |
2086 |
| |
2087 fp = g_fopen(xfer->local_filename, "wb"); |
| |
2088 if(! fp) { |
| |
2089 mwFileTransfer_cancel(ft); |
| |
2090 return; |
| |
2091 } |
| |
2092 |
| |
2093 xfer->dest_fp = fp; |
| |
2094 mwFileTransfer_accept(ft); |
| |
2095 } |
| |
2096 |
| |
2097 |
| |
2098 static void mw_ft_offered(struct mwFileTransfer *ft) { |
| |
2099 /* |
| |
2100 - create a gaim ft object |
| |
2101 - offer it |
| |
2102 */ |
| |
2103 |
| |
2104 struct mwServiceFileTransfer *srvc; |
| |
2105 struct mwSession *session; |
| |
2106 struct mwGaimPluginData *pd; |
| |
2107 GaimConnection *gc; |
| |
2108 GaimAccount *acct; |
| |
2109 const char *who; |
| |
2110 GaimXfer *xfer; |
| |
2111 |
| |
2112 /* @todo add some safety checks */ |
| |
2113 srvc = mwFileTransfer_getService(ft); |
| |
2114 session = mwService_getSession(MW_SERVICE(srvc)); |
| |
2115 pd = mwSession_getClientData(session); |
| |
2116 gc = pd->gc; |
| |
2117 acct = gaim_connection_get_account(gc); |
| |
2118 |
| |
2119 who = mwFileTransfer_getUser(ft)->user; |
| |
2120 |
| |
2121 DEBUG_INFO("file transfer %p offered\n", ft); |
| |
2122 DEBUG_INFO(" from: %s\n", NSTR(who)); |
| |
2123 DEBUG_INFO(" file: %s\n", NSTR(mwFileTransfer_getFileName(ft))); |
| |
2124 DEBUG_INFO(" size: %u\n", mwFileTransfer_getFileSize(ft)); |
| |
2125 DEBUG_INFO(" text: %s\n", NSTR(mwFileTransfer_getMessage(ft))); |
| |
2126 |
| |
2127 xfer = gaim_xfer_new(acct, GAIM_XFER_RECEIVE, who); |
| |
2128 if (xfer) |
| |
2129 { |
| |
2130 gaim_xfer_ref(xfer); |
| |
2131 mwFileTransfer_setClientData(ft, xfer, (GDestroyNotify) gaim_xfer_unref); |
| |
2132 xfer->data = ft; |
| |
2133 |
| |
2134 gaim_xfer_set_init_fnc(xfer, ft_incoming_init); |
| |
2135 gaim_xfer_set_cancel_recv_fnc(xfer, ft_incoming_cancel); |
| |
2136 gaim_xfer_set_request_denied_fnc(xfer, ft_incoming_cancel); |
| |
2137 |
| |
2138 gaim_xfer_set_filename(xfer, mwFileTransfer_getFileName(ft)); |
| |
2139 gaim_xfer_set_size(xfer, mwFileTransfer_getFileSize(ft)); |
| |
2140 gaim_xfer_set_message(xfer, mwFileTransfer_getMessage(ft)); |
| |
2141 |
| |
2142 gaim_xfer_request(xfer); |
| |
2143 } |
| |
2144 } |
| |
2145 |
| |
2146 |
| |
2147 static void ft_send(struct mwFileTransfer *ft, FILE *fp) { |
| |
2148 guchar buf[MW_FT_LEN]; |
| |
2149 struct mwOpaque o = { .data = buf, .len = MW_FT_LEN }; |
| |
2150 guint32 rem; |
| |
2151 GaimXfer *xfer; |
| |
2152 |
| |
2153 xfer = mwFileTransfer_getClientData(ft); |
| |
2154 |
| |
2155 rem = mwFileTransfer_getRemaining(ft); |
| |
2156 if(rem < MW_FT_LEN) o.len = rem; |
| |
2157 |
| |
2158 if(fread(buf, (size_t) o.len, 1, fp)) { |
| |
2159 |
| |
2160 /* calculate progress and display it */ |
| |
2161 xfer->bytes_sent += o.len; |
| |
2162 xfer->bytes_remaining -= o.len; |
| |
2163 gaim_xfer_update_progress(xfer); |
| |
2164 |
| |
2165 mwFileTransfer_send(ft, &o); |
| |
2166 |
| |
2167 } else { |
| |
2168 int err = errno; |
| |
2169 DEBUG_WARN("problem reading from file %s: %s\n", |
| |
2170 NSTR(mwFileTransfer_getFileName(ft)), strerror(err)); |
| |
2171 |
| |
2172 mwFileTransfer_cancel(ft); |
| |
2173 } |
| |
2174 } |
| |
2175 |
| |
2176 |
| |
2177 static void mw_ft_opened(struct mwFileTransfer *ft) { |
| |
2178 /* |
| |
2179 - get gaim ft from client data in ft |
| |
2180 - set the state to active |
| |
2181 */ |
| |
2182 |
| |
2183 GaimXfer *xfer; |
| |
2184 |
| |
2185 xfer = mwFileTransfer_getClientData(ft); |
| |
2186 |
| |
2187 if(! xfer) { |
| |
2188 mwFileTransfer_cancel(ft); |
| |
2189 mwFileTransfer_free(ft); |
| |
2190 g_return_if_reached(); |
| |
2191 } |
| |
2192 |
| |
2193 if(gaim_xfer_get_type(xfer) == GAIM_XFER_SEND) { |
| |
2194 xfer->dest_fp = g_fopen(xfer->local_filename, "rb"); |
| |
2195 ft_send(ft, xfer->dest_fp); |
| |
2196 } |
| |
2197 } |
| |
2198 |
| |
2199 |
| |
2200 static void mw_ft_closed(struct mwFileTransfer *ft, guint32 code) { |
| |
2201 /* |
| |
2202 - get gaim ft from client data in ft |
| |
2203 - indicate rejection/cancelation/completion |
| |
2204 - free the file transfer itself |
| |
2205 */ |
| |
2206 |
| |
2207 GaimXfer *xfer; |
| |
2208 |
| |
2209 xfer = mwFileTransfer_getClientData(ft); |
| |
2210 if(xfer) { |
| |
2211 xfer->data = NULL; |
| |
2212 |
| |
2213 if(! mwFileTransfer_getRemaining(ft)) { |
| |
2214 gaim_xfer_set_completed(xfer, TRUE); |
| |
2215 gaim_xfer_end(xfer); |
| |
2216 |
| |
2217 } else if(mwFileTransfer_isCancelLocal(ft)) { |
| |
2218 /* calling gaim_xfer_cancel_local is redundant, since that's |
| |
2219 probably what triggered this function to be called */ |
| |
2220 ; |
| |
2221 |
| |
2222 } else if(mwFileTransfer_isCancelRemote(ft)) { |
| |
2223 /* steal the reference for the xfer */ |
| |
2224 mwFileTransfer_setClientData(ft, NULL, NULL); |
| |
2225 gaim_xfer_cancel_remote(xfer); |
| |
2226 |
| |
2227 /* drop the stolen reference */ |
| |
2228 gaim_xfer_unref(xfer); |
| |
2229 return; |
| |
2230 } |
| |
2231 } |
| |
2232 |
| |
2233 mwFileTransfer_free(ft); |
| |
2234 } |
| |
2235 |
| |
2236 |
| |
2237 static void mw_ft_recv(struct mwFileTransfer *ft, |
| |
2238 struct mwOpaque *data) { |
| |
2239 /* |
| |
2240 - get gaim ft from client data in ft |
| |
2241 - update transfered percentage |
| |
2242 - if done, destroy the ft, disassociate from gaim ft |
| |
2243 */ |
| |
2244 |
| |
2245 GaimXfer *xfer; |
| |
2246 FILE *fp; |
| |
2247 |
| |
2248 xfer = mwFileTransfer_getClientData(ft); |
| |
2249 g_return_if_fail(xfer != NULL); |
| |
2250 |
| |
2251 fp = xfer->dest_fp; |
| |
2252 g_return_if_fail(fp != NULL); |
| |
2253 |
| |
2254 /* we must collect and save our precious data */ |
| |
2255 fwrite(data->data, 1, data->len, fp); |
| |
2256 |
| |
2257 /* update the progress */ |
| |
2258 xfer->bytes_sent += data->len; |
| |
2259 xfer->bytes_remaining -= data->len; |
| |
2260 gaim_xfer_update_progress(xfer); |
| |
2261 |
| |
2262 /* let the other side know we got it, and to send some more */ |
| |
2263 mwFileTransfer_ack(ft); |
| |
2264 } |
| |
2265 |
| |
2266 |
| |
2267 static void mw_ft_ack(struct mwFileTransfer *ft) { |
| |
2268 GaimXfer *xfer; |
| |
2269 |
| |
2270 xfer = mwFileTransfer_getClientData(ft); |
| |
2271 g_return_if_fail(xfer != NULL); |
| |
2272 g_return_if_fail(xfer->watcher == 0); |
| |
2273 |
| |
2274 if(! mwFileTransfer_getRemaining(ft)) { |
| |
2275 gaim_xfer_set_completed(xfer, TRUE); |
| |
2276 gaim_xfer_end(xfer); |
| |
2277 |
| |
2278 } else if(mwFileTransfer_isOpen(ft)) { |
| |
2279 ft_send(ft, xfer->dest_fp); |
| |
2280 } |
| |
2281 } |
| |
2282 |
| |
2283 |
| |
2284 static void mw_ft_clear(struct mwServiceFileTransfer *srvc) { |
| |
2285 ; |
| |
2286 } |
| |
2287 |
| |
2288 |
| |
2289 static struct mwFileTransferHandler mw_ft_handler = { |
| |
2290 .ft_offered = mw_ft_offered, |
| |
2291 .ft_opened = mw_ft_opened, |
| |
2292 .ft_closed = mw_ft_closed, |
| |
2293 .ft_recv = mw_ft_recv, |
| |
2294 .ft_ack = mw_ft_ack, |
| |
2295 .clear = mw_ft_clear, |
| |
2296 }; |
| |
2297 |
| |
2298 |
| |
2299 static struct mwServiceFileTransfer *mw_srvc_ft_new(struct mwSession *s) { |
| |
2300 struct mwServiceFileTransfer *srvc; |
| |
2301 GHashTable *ft_map; |
| |
2302 |
| |
2303 ft_map = g_hash_table_new(g_direct_hash, g_direct_equal); |
| |
2304 |
| |
2305 srvc = mwServiceFileTransfer_new(s, &mw_ft_handler); |
| |
2306 mwService_setClientData(MW_SERVICE(srvc), ft_map, |
| |
2307 (GDestroyNotify) g_hash_table_destroy); |
| |
2308 |
| |
2309 return srvc; |
| |
2310 } |
| |
2311 |
| |
2312 |
| |
2313 static void convo_data_free(struct convo_data *cd) { |
| |
2314 GList *l; |
| |
2315 |
| |
2316 /* clean the queue */ |
| |
2317 for(l = cd->queue; l; l = g_list_delete_link(l, l)) { |
| |
2318 struct convo_msg *m = l->data; |
| |
2319 if(m->clear) m->clear(m->data); |
| |
2320 g_free(m); |
| |
2321 } |
| |
2322 |
| |
2323 g_free(cd); |
| |
2324 } |
| |
2325 |
| |
2326 |
| |
2327 /** allocates a convo_data structure and associates it with the |
| |
2328 conversation in the client data slot */ |
| |
2329 static void convo_data_new(struct mwConversation *conv) { |
| |
2330 struct convo_data *cd; |
| |
2331 |
| |
2332 g_return_if_fail(conv != NULL); |
| |
2333 |
| |
2334 if(mwConversation_getClientData(conv)) |
| |
2335 return; |
| |
2336 |
| |
2337 cd = g_new0(struct convo_data, 1); |
| |
2338 cd->conv = conv; |
| |
2339 |
| |
2340 mwConversation_setClientData(conv, cd, (GDestroyNotify) convo_data_free); |
| |
2341 } |
| |
2342 |
| |
2343 |
| |
2344 static GaimConversation *convo_get_gconv(struct mwConversation *conv) { |
| |
2345 struct mwServiceIm *srvc; |
| |
2346 struct mwSession *session; |
| |
2347 struct mwGaimPluginData *pd; |
| |
2348 GaimConnection *gc; |
| |
2349 GaimAccount *acct; |
| |
2350 |
| |
2351 struct mwIdBlock *idb; |
| |
2352 |
| |
2353 srvc = mwConversation_getService(conv); |
| |
2354 session = mwService_getSession(MW_SERVICE(srvc)); |
| |
2355 pd = mwSession_getClientData(session); |
| |
2356 gc = pd->gc; |
| |
2357 acct = gaim_connection_get_account(gc); |
| |
2358 |
| |
2359 idb = mwConversation_getTarget(conv); |
| |
2360 |
| |
2361 return gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, |
| |
2362 idb->user, acct); |
| |
2363 } |
| |
2364 |
| |
2365 |
| |
2366 static void convo_queue(struct mwConversation *conv, |
| |
2367 enum mwImSendType type, gconstpointer data) { |
| |
2368 |
| |
2369 struct convo_data *cd; |
| |
2370 struct convo_msg *m; |
| |
2371 |
| |
2372 convo_data_new(conv); |
| |
2373 cd = mwConversation_getClientData(conv); |
| |
2374 |
| |
2375 m = g_new0(struct convo_msg, 1); |
| |
2376 m->type = type; |
| |
2377 |
| |
2378 switch(type) { |
| |
2379 case mwImSend_PLAIN: |
| |
2380 m->data = g_strdup(data); |
| |
2381 m->clear = g_free; |
| |
2382 break; |
| |
2383 |
| |
2384 case mwImSend_TYPING: |
| |
2385 default: |
| |
2386 m->data = (gpointer) data; |
| |
2387 m->clear = NULL; |
| |
2388 } |
| |
2389 |
| |
2390 cd->queue = g_list_append(cd->queue, m); |
| |
2391 } |
| |
2392 |
| |
2393 |
| |
2394 /* Does what it takes to get an error displayed for a conversation */ |
| |
2395 static void convo_error(struct mwConversation *conv, guint32 err) { |
| |
2396 GaimConversation *gconv; |
| |
2397 char *tmp, *text; |
| |
2398 struct mwIdBlock *idb; |
| |
2399 |
| |
2400 idb = mwConversation_getTarget(conv); |
| |
2401 |
| |
2402 tmp = mwError(err); |
| |
2403 text = g_strconcat(_("Unable to send message: "), tmp, NULL); |
| |
2404 |
| |
2405 gconv = convo_get_gconv(conv); |
| |
2406 if(gconv && !gaim_conv_present_error(idb->user, gconv->account, text)) { |
| |
2407 |
| |
2408 g_free(text); |
| |
2409 text = g_strdup_printf(_("Unable to send message to %s:"), |
| |
2410 (idb->user)? idb->user: "(unknown)"); |
| |
2411 gaim_notify_error(gaim_account_get_connection(gconv->account), |
| |
2412 NULL, text, tmp); |
| |
2413 } |
| |
2414 |
| |
2415 g_free(tmp); |
| |
2416 g_free(text); |
| |
2417 } |
| |
2418 |
| |
2419 |
| |
2420 static void convo_queue_send(struct mwConversation *conv) { |
| |
2421 struct convo_data *cd; |
| |
2422 GList *l; |
| |
2423 |
| |
2424 cd = mwConversation_getClientData(conv); |
| |
2425 |
| |
2426 for(l = cd->queue; l; l = g_list_delete_link(l, l)) { |
| |
2427 struct convo_msg *m = l->data; |
| |
2428 |
| |
2429 mwConversation_send(conv, m->type, m->data); |
| |
2430 |
| |
2431 if(m->clear) m->clear(m->data); |
| |
2432 g_free(m); |
| |
2433 } |
| |
2434 |
| |
2435 cd->queue = NULL; |
| |
2436 } |
| |
2437 |
| |
2438 |
| |
2439 /** called when a mw conversation leaves a gaim conversation to |
| |
2440 inform the gaim conversation that it's unsafe to offer any *cool* |
| |
2441 features. */ |
| |
2442 static void convo_nofeatures(struct mwConversation *conv) { |
| |
2443 GaimConversation *gconv; |
| |
2444 GaimConnection *gc; |
| |
2445 |
| |
2446 gconv = convo_get_gconv(conv); |
| |
2447 if(! gconv) return; |
| |
2448 |
| |
2449 gc = gaim_conversation_get_gc(gconv); |
| |
2450 if(! gc) return; |
| |
2451 |
| |
2452 gaim_conversation_set_features(gconv, gc->flags); |
| |
2453 } |
| |
2454 |
| |
2455 |
| |
2456 /** called when a mw conversation and gaim conversation come together, |
| |
2457 to inform the gaim conversation of what features to offer the |
| |
2458 user */ |
| |
2459 static void convo_features(struct mwConversation *conv) { |
| |
2460 GaimConversation *gconv; |
| |
2461 GaimConnectionFlags feat; |
| |
2462 |
| |
2463 gconv = convo_get_gconv(conv); |
| |
2464 if(! gconv) return; |
| |
2465 |
| |
2466 feat = gaim_conversation_get_features(gconv); |
| |
2467 |
| |
2468 if(mwConversation_isOpen(conv)) { |
| |
2469 if(mwConversation_supports(conv, mwImSend_HTML)) { |
| |
2470 feat |= GAIM_CONNECTION_HTML; |
| |
2471 } else { |
| |
2472 feat &= ~GAIM_CONNECTION_HTML; |
| |
2473 } |
| |
2474 |
| |
2475 if(mwConversation_supports(conv, mwImSend_MIME)) { |
| |
2476 feat &= ~GAIM_CONNECTION_NO_IMAGES; |
| |
2477 } else { |
| |
2478 feat |= GAIM_CONNECTION_NO_IMAGES; |
| |
2479 } |
| |
2480 |
| |
2481 DEBUG_INFO("conversation features set to 0x%04x\n", feat); |
| |
2482 gaim_conversation_set_features(gconv, feat); |
| |
2483 |
| |
2484 } else { |
| |
2485 convo_nofeatures(conv); |
| |
2486 } |
| |
2487 } |
| |
2488 |
| |
2489 |
| |
2490 static void mw_conversation_opened(struct mwConversation *conv) { |
| |
2491 struct mwServiceIm *srvc; |
| |
2492 struct mwSession *session; |
| |
2493 struct mwGaimPluginData *pd; |
| |
2494 GaimConnection *gc; |
| |
2495 GaimAccount *acct; |
| |
2496 |
| |
2497 struct convo_dat *cd; |
| |
2498 |
| |
2499 srvc = mwConversation_getService(conv); |
| |
2500 session = mwService_getSession(MW_SERVICE(srvc)); |
| |
2501 pd = mwSession_getClientData(session); |
| |
2502 gc = pd->gc; |
| |
2503 acct = gaim_connection_get_account(gc); |
| |
2504 |
| |
2505 /* set up the queue */ |
| |
2506 cd = mwConversation_getClientData(conv); |
| |
2507 if(cd) { |
| |
2508 convo_queue_send(conv); |
| |
2509 |
| |
2510 if(! convo_get_gconv(conv)) { |
| |
2511 mwConversation_free(conv); |
| |
2512 return; |
| |
2513 } |
| |
2514 |
| |
2515 } else { |
| |
2516 convo_data_new(conv); |
| |
2517 } |
| |
2518 |
| |
2519 { /* record the client key for the buddy */ |
| |
2520 GaimBuddy *buddy; |
| |
2521 struct mwLoginInfo *info; |
| |
2522 info = mwConversation_getTargetInfo(conv); |
| |
2523 |
| |
2524 buddy = gaim_find_buddy(acct, info->user_id); |
| |
2525 if(buddy) { |
| |
2526 gaim_blist_node_set_int((GaimBlistNode *) buddy, |
| |
2527 BUDDY_KEY_CLIENT, info->type); |
| |
2528 } |
| |
2529 } |
| |
2530 |
| |
2531 convo_features(conv); |
| |
2532 } |
| |
2533 |
| |
2534 |
| |
2535 static void mw_conversation_closed(struct mwConversation *conv, |
| |
2536 guint32 reason) { |
| |
2537 |
| |
2538 struct convo_data *cd; |
| |
2539 |
| |
2540 g_return_if_fail(conv != NULL); |
| |
2541 |
| |
2542 /* if there's an error code and a non-typing message in the queue, |
| |
2543 print an error message to the conversation */ |
| |
2544 cd = mwConversation_getClientData(conv); |
| |
2545 if(reason && cd && cd->queue) { |
| |
2546 GList *l; |
| |
2547 for(l = cd->queue; l; l = l->next) { |
| |
2548 struct convo_msg *m = l->data; |
| |
2549 if(m->type != mwImSend_TYPING) { |
| |
2550 convo_error(conv, reason); |
| |
2551 break; |
| |
2552 } |
| |
2553 } |
| |
2554 } |
| |
2555 |
| |
2556 #if 0 |
| |
2557 /* don't do this, to prevent the occasional weird sending of |
| |
2558 formatted messages as plaintext when the other end closes the |
| |
2559 conversation after we've begun composing the message */ |
| |
2560 convo_nofeatures(conv); |
| |
2561 #endif |
| |
2562 |
| |
2563 mwConversation_removeClientData(conv); |
| |
2564 } |
| |
2565 |
| |
2566 |
| |
2567 static void im_recv_text(struct mwConversation *conv, |
| |
2568 struct mwGaimPluginData *pd, |
| |
2569 const char *msg) { |
| |
2570 |
| |
2571 struct mwIdBlock *idb; |
| |
2572 char *txt, *esc; |
| |
2573 const char *t; |
| |
2574 |
| |
2575 idb = mwConversation_getTarget(conv); |
| |
2576 |
| |
2577 txt = gaim_utf8_try_convert(msg); |
| |
2578 t = txt? txt: msg; |
| |
2579 |
| |
2580 esc = g_markup_escape_text(t, -1); |
| |
2581 serv_got_im(pd->gc, idb->user, esc, 0, time(NULL)); |
| |
2582 g_free(esc); |
| |
2583 |
| |
2584 g_free(txt); |
| |
2585 } |
| |
2586 |
| |
2587 |
| |
2588 static void im_recv_typing(struct mwConversation *conv, |
| |
2589 struct mwGaimPluginData *pd, |
| |
2590 gboolean typing) { |
| |
2591 |
| |
2592 struct mwIdBlock *idb; |
| |
2593 idb = mwConversation_getTarget(conv); |
| |
2594 |
| |
2595 serv_got_typing(pd->gc, idb->user, 0, |
| |
2596 typing? GAIM_TYPING: GAIM_NOT_TYPING); |
| |
2597 } |
| |
2598 |
| |
2599 |
| |
2600 static void im_recv_html(struct mwConversation *conv, |
| |
2601 struct mwGaimPluginData *pd, |
| |
2602 const char *msg) { |
| |
2603 struct mwIdBlock *idb; |
| |
2604 char *t1, *t2; |
| |
2605 const char *t; |
| |
2606 |
| |
2607 idb = mwConversation_getTarget(conv); |
| |
2608 |
| |
2609 /* ensure we're receiving UTF8 */ |
| |
2610 t1 = gaim_utf8_try_convert(msg); |
| |
2611 t = t1? t1: msg; |
| |
2612 |
| |
2613 /* convert entities to UTF8 so they'll log correctly */ |
| |
2614 t2 = gaim_utf8_ncr_decode(t); |
| |
2615 t = t2? t2: t; |
| |
2616 |
| |
2617 serv_got_im(pd->gc, idb->user, t, 0, time(NULL)); |
| |
2618 |
| |
2619 g_free(t1); |
| |
2620 g_free(t2); |
| |
2621 } |
| |
2622 |
| |
2623 |
| |
2624 static void im_recv_subj(struct mwConversation *conv, |
| |
2625 struct mwGaimPluginData *pd, |
| |
2626 const char *subj) { |
| |
2627 |
| |
2628 /** @todo somehow indicate receipt of a conversation subject. It |
| |
2629 would also be nice if we added a /topic command for the |
| |
2630 protocol */ |
| |
2631 ; |
| |
2632 } |
| |
2633 |
| |
2634 |
| |
2635 /** generate "cid:908@20582notesbuddy" from "<908@20582notesbuddy>" */ |
| |
2636 static char *make_cid(const char *cid) { |
| |
2637 gsize n; |
| |
2638 char *c, *d; |
| |
2639 |
| |
2640 g_return_val_if_fail(cid != NULL, NULL); |
| |
2641 |
| |
2642 n = strlen(cid); |
| |
2643 g_return_val_if_fail(n > 2, NULL); |
| |
2644 |
| |
2645 c = g_strndup(cid+1, n-2); |
| |
2646 d = g_strdup_printf("cid:%s", c); |
| |
2647 |
| |
2648 g_free(c); |
| |
2649 return d; |
| |
2650 } |
| |
2651 |
| |
2652 |
| |
2653 static void im_recv_mime(struct mwConversation *conv, |
| |
2654 struct mwGaimPluginData *pd, |
| |
2655 const char *data) { |
| |
2656 |
| |
2657 GHashTable *img_by_cid; |
| |
2658 GList *images; |
| |
2659 |
| |
2660 GString *str; |
| |
2661 |
| |
2662 GaimMimeDocument *doc; |
| |
2663 const GList *parts; |
| |
2664 |
| |
2665 img_by_cid = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); |
| |
2666 images = NULL; |
| |
2667 |
| |
2668 /* don't want the contained string to ever be NULL */ |
| |
2669 str = g_string_new(""); |
| |
2670 |
| |
2671 doc = gaim_mime_document_parse(data); |
| |
2672 |
| |
2673 /* handle all the MIME parts */ |
| |
2674 parts = gaim_mime_document_get_parts(doc); |
| |
2675 for(; parts; parts = parts->next) { |
| |
2676 GaimMimePart *part = parts->data; |
| |
2677 const char *type; |
| |
2678 |
| |
2679 type = gaim_mime_part_get_field(part, "content-type"); |
| |
2680 DEBUG_INFO("MIME part Content-Type: %s\n", NSTR(type)); |
| |
2681 |
| |
2682 if(! type) { |
| |
2683 ; /* feh */ |
| |
2684 |
| |
2685 } else if(gaim_str_has_prefix(type, "image")) { |
| |
2686 /* put images into the image store */ |
| |
2687 |
| |
2688 guchar *d_dat; |
| |
2689 gsize d_len; |
| |
2690 char *cid; |
| |
2691 int img; |
| |
2692 |
| |
2693 /* obtain and unencode the data */ |
| |
2694 gaim_mime_part_get_data_decoded(part, &d_dat, &d_len); |
| |
2695 |
| |
2696 /* look up the content id */ |
| |
2697 cid = (char *) gaim_mime_part_get_field(part, "Content-ID"); |
| |
2698 cid = make_cid(cid); |
| |
2699 |
| |
2700 /* add image to the gaim image store */ |
| |
2701 img = gaim_imgstore_add(d_dat, d_len, cid); |
| |
2702 g_free(d_dat); |
| |
2703 |
| |
2704 /* map the cid to the image store identifier */ |
| |
2705 g_hash_table_insert(img_by_cid, cid, GINT_TO_POINTER(img)); |
| |
2706 |
| |
2707 /* recall the image for dereferencing later */ |
| |
2708 images = g_list_append(images, GINT_TO_POINTER(img)); |
| |
2709 |
| |
2710 } else if(gaim_str_has_prefix(type, "text")) { |
| |
2711 |
| |
2712 /* concatenate all the text parts together */ |
| |
2713 guchar *data; |
| |
2714 gsize len; |
| |
2715 |
| |
2716 gaim_mime_part_get_data_decoded(part, &data, &len); |
| |
2717 g_string_append(str, (const char *)data); |
| |
2718 g_free(data); |
| |
2719 } |
| |
2720 } |
| |
2721 |
| |
2722 gaim_mime_document_free(doc); |
| |
2723 |
| |
2724 /* @todo should put this in its own function */ |
| |
2725 { /* replace each IMG tag's SRC attribute with an ID attribute. This |
| |
2726 actually modifies the contents of str */ |
| |
2727 GData *attribs; |
| |
2728 char *start, *end; |
| |
2729 char *tmp = str->str; |
| |
2730 |
| |
2731 while(*tmp && gaim_markup_find_tag("img", tmp, (const char **) &start, |
| |
2732 (const char **) &end, &attribs)) { |
| |
2733 |
| |
2734 char *alt, *align, *border, *src; |
| |
2735 int img = 0; |
| |
2736 |
| |
2737 alt = g_datalist_get_data(&attribs, "alt"); |
| |
2738 align = g_datalist_get_data(&attribs, "align"); |
| |
2739 border = g_datalist_get_data(&attribs, "border"); |
| |
2740 src = g_datalist_get_data(&attribs, "src"); |
| |
2741 |
| |
2742 if(src) |
| |
2743 img = GPOINTER_TO_INT(g_hash_table_lookup(img_by_cid, src)); |
| |
2744 |
| |
2745 if(img) { |
| |
2746 GString *atstr; |
| |
2747 gsize len = (end - start); |
| |
2748 gsize mov; |
| |
2749 |
| |
2750 atstr = g_string_new(""); |
| |
2751 if(alt) g_string_append_printf(atstr, " alt=\"%s\"", alt); |
| |
2752 if(align) g_string_append_printf(atstr, " align=\"%s\"", align); |
| |
2753 if(border) g_string_append_printf(atstr, " border=\"%s\"", border); |
| |
2754 |
| |
2755 mov = g_snprintf(start, len, "<img%s id=\"%i\"", atstr->str, img); |
| |
2756 while(mov < len) start[mov++] = ' '; |
| |
2757 |
| |
2758 g_string_free(atstr, TRUE); |
| |
2759 } |
| |
2760 |
| |
2761 g_datalist_clear(&attribs); |
| |
2762 tmp = end + 1; |
| |
2763 } |
| |
2764 } |
| |
2765 |
| |
2766 im_recv_html(conv, pd, str->str); |
| |
2767 |
| |
2768 g_string_free(str, TRUE); |
| |
2769 |
| |
2770 /* clean up the cid table */ |
| |
2771 g_hash_table_destroy(img_by_cid); |
| |
2772 |
| |
2773 /* dereference all the imgages */ |
| |
2774 while(images) { |
| |
2775 gaim_imgstore_unref(GPOINTER_TO_INT(images->data)); |
| |
2776 images = g_list_delete_link(images, images); |
| |
2777 } |
| |
2778 } |
| |
2779 |
| |
2780 |
| |
2781 static void mw_conversation_recv(struct mwConversation *conv, |
| |
2782 enum mwImSendType type, |
| |
2783 gconstpointer msg) { |
| |
2784 struct mwServiceIm *srvc; |
| |
2785 struct mwSession *session; |
| |
2786 struct mwGaimPluginData *pd; |
| |
2787 |
| |
2788 srvc = mwConversation_getService(conv); |
| |
2789 session = mwService_getSession(MW_SERVICE(srvc)); |
| |
2790 pd = mwSession_getClientData(session); |
| |
2791 |
| |
2792 switch(type) { |
| |
2793 case mwImSend_PLAIN: |
| |
2794 im_recv_text(conv, pd, msg); |
| |
2795 break; |
| |
2796 |
| |
2797 case mwImSend_TYPING: |
| |
2798 im_recv_typing(conv, pd, !! msg); |
| |
2799 break; |
| |
2800 |
| |
2801 case mwImSend_HTML: |
| |
2802 im_recv_html(conv, pd, msg); |
| |
2803 break; |
| |
2804 |
| |
2805 case mwImSend_SUBJECT: |
| |
2806 im_recv_subj(conv, pd, msg); |
| |
2807 break; |
| |
2808 |
| |
2809 case mwImSend_MIME: |
| |
2810 im_recv_mime(conv, pd, msg); |
| |
2811 break; |
| |
2812 |
| |
2813 default: |
| |
2814 DEBUG_INFO("conversation received strange type, 0x%04x\n", type); |
| |
2815 ; /* erm... */ |
| |
2816 } |
| |
2817 } |
| |
2818 |
| |
2819 |
| |
2820 static void mw_place_invite(struct mwConversation *conv, |
| |
2821 const char *message, |
| |
2822 const char *title, const char *name) { |
| |
2823 struct mwServiceIm *srvc; |
| |
2824 struct mwSession *session; |
| |
2825 struct mwGaimPluginData *pd; |
| |
2826 |
| |
2827 struct mwIdBlock *idb; |
| |
2828 GHashTable *ht; |
| |
2829 |
| |
2830 srvc = mwConversation_getService(conv); |
| |
2831 session = mwService_getSession(MW_SERVICE(srvc)); |
| |
2832 pd = mwSession_getClientData(session); |
| |
2833 |
| |
2834 idb = mwConversation_getTarget(conv); |
| |
2835 |
| |
2836 ht = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free); |
| |
2837 g_hash_table_insert(ht, CHAT_KEY_CREATOR, g_strdup(idb->user)); |
| |
2838 g_hash_table_insert(ht, CHAT_KEY_NAME, g_strdup(name)); |
| |
2839 g_hash_table_insert(ht, CHAT_KEY_TOPIC, g_strdup(title)); |
| |
2840 g_hash_table_insert(ht, CHAT_KEY_INVITE, g_strdup(message)); |
| |
2841 g_hash_table_insert(ht, CHAT_KEY_IS_PLACE, g_strdup("")); /* ugh */ |
| |
2842 |
| |
2843 if(! title) title = "(no title)"; |
| |
2844 if(! message) message = "(no message)"; |
| |
2845 serv_got_chat_invite(pd->gc, title, idb->user, message, ht); |
| |
2846 |
| |
2847 mwConversation_close(conv, ERR_SUCCESS); |
| |
2848 mwConversation_free(conv); |
| |
2849 } |
| |
2850 |
| |
2851 |
| |
2852 static void mw_im_clear(struct mwServiceIm *srvc) { |
| |
2853 ; |
| |
2854 } |
| |
2855 |
| |
2856 |
| |
2857 static struct mwImHandler mw_im_handler = { |
| |
2858 .conversation_opened = mw_conversation_opened, |
| |
2859 .conversation_closed = mw_conversation_closed, |
| |
2860 .conversation_recv = mw_conversation_recv, |
| |
2861 .place_invite = mw_place_invite, |
| |
2862 .clear = mw_im_clear, |
| |
2863 }; |
| |
2864 |
| |
2865 |
| |
2866 static struct mwServiceIm *mw_srvc_im_new(struct mwSession *s) { |
| |
2867 struct mwServiceIm *srvc; |
| |
2868 srvc = mwServiceIm_new(s, &mw_im_handler); |
| |
2869 mwServiceIm_setClientType(srvc, mwImClient_NOTESBUDDY); |
| |
2870 return srvc; |
| |
2871 } |
| |
2872 |
| |
2873 |
| |
2874 /* The following helps us relate a mwPlace to a GaimConvChat in the |
| |
2875 various forms by which either may be indicated. Uses some of |
| |
2876 the similar macros from the conference service above */ |
| |
2877 |
| |
2878 #define PLACE_TO_ID(place) (GPOINTER_TO_INT(place)) |
| |
2879 #define ID_TO_PLACE(pd, id) (place_find_by_id((pd), (id))) |
| |
2880 |
| |
2881 #define CHAT_TO_PLACE(pd, chat) (ID_TO_PLACE((pd), CHAT_TO_ID(chat))) |
| |
2882 #define PLACE_TO_CHAT(place) (ID_TO_CHAT(PLACE_TO_ID(place))) |
| |
2883 |
| |
2884 |
| |
2885 static struct mwPlace * |
| |
2886 place_find_by_id(struct mwGaimPluginData *pd, int id) { |
| |
2887 struct mwServicePlace *srvc = pd->srvc_place; |
| |
2888 struct mwPlace *place = NULL; |
| |
2889 GList *l; |
| |
2890 |
| |
2891 l = (GList *) mwServicePlace_getPlaces(srvc); |
| |
2892 for(; l; l = l->next) { |
| |
2893 struct mwPlace *p = l->data; |
| |
2894 GaimConvChat *h = GAIM_CONV_CHAT(mwPlace_getClientData(p)); |
| |
2895 |
| |
2896 if(CHAT_TO_ID(h) == id) { |
| |
2897 place = p; |
| |
2898 break; |
| |
2899 } |
| |
2900 } |
| |
2901 |
| |
2902 return place; |
| |
2903 } |
| |
2904 |
| |
2905 |
| |
2906 static void mw_place_opened(struct mwPlace *place) { |
| |
2907 struct mwServicePlace *srvc; |
| |
2908 struct mwSession *session; |
| |
2909 struct mwGaimPluginData *pd; |
| |
2910 GaimConnection *gc; |
| |
2911 GaimConversation *gconf; |
| |
2912 |
| |
2913 GList *members, *l; |
| |
2914 |
| |
2915 const char *n = mwPlace_getName(place); |
| |
2916 const char *t = mwPlace_getTitle(place); |
| |
2917 |
| |
2918 srvc = mwPlace_getService(place); |
| |
2919 session = mwService_getSession(MW_SERVICE(srvc)); |
| |
2920 pd = mwSession_getClientData(session); |
| |
2921 gc = pd->gc; |
| |
2922 |
| |
2923 members = mwPlace_getMembers(place); |
| |
2924 |
| |
2925 DEBUG_INFO("place %s opened, %u initial members\n", |
| |
2926 NSTR(n), g_list_length(members)); |
| |
2927 |
| |
2928 if(! t) t = "(no title)"; |
| |
2929 gconf = serv_got_joined_chat(gc, PLACE_TO_ID(place), t); |
| |
2930 |
| |
2931 mwPlace_setClientData(place, gconf, NULL); |
| |
2932 |
| |
2933 for(l = members; l; l = l->next) { |
| |
2934 struct mwIdBlock *idb = l->data; |
| |
2935 gaim_conv_chat_add_user(GAIM_CONV_CHAT(gconf), idb->user, |
| |
2936 NULL, GAIM_CBFLAGS_NONE, FALSE); |
| |
2937 } |
| |
2938 g_list_free(members); |
| |
2939 } |
| |
2940 |
| |
2941 |
| |
2942 static void mw_place_closed(struct mwPlace *place, guint32 code) { |
| |
2943 struct mwServicePlace *srvc; |
| |
2944 struct mwSession *session; |
| |
2945 struct mwGaimPluginData *pd; |
| |
2946 GaimConnection *gc; |
| |
2947 |
| |
2948 const char *n = mwPlace_getName(place); |
| |
2949 char *msg = mwError(code); |
| |
2950 |
| |
2951 DEBUG_INFO("place %s closed, 0x%08x\n", NSTR(n), code); |
| |
2952 |
| |
2953 srvc = mwPlace_getService(place); |
| |
2954 session = mwService_getSession(MW_SERVICE(srvc)); |
| |
2955 pd = mwSession_getClientData(session); |
| |
2956 gc = pd->gc; |
| |
2957 |
| |
2958 serv_got_chat_left(gc, PLACE_TO_ID(place)); |
| |
2959 |
| |
2960 gaim_notify_error(gc, _("Place Closed"), NULL, msg); |
| |
2961 g_free(msg); |
| |
2962 } |
| |
2963 |
| |
2964 |
| |
2965 static void mw_place_peerJoined(struct mwPlace *place, |
| |
2966 const struct mwIdBlock *peer) { |
| |
2967 struct mwServicePlace *srvc; |
| |
2968 struct mwSession *session; |
| |
2969 struct mwGaimPluginData *pd; |
| |
2970 GaimConnection *gc; |
| |
2971 GaimConversation *gconf; |
| |
2972 |
| |
2973 const char *n = mwPlace_getName(place); |
| |
2974 |
| |
2975 DEBUG_INFO("%s joined place %s\n", NSTR(peer->user), NSTR(n)); |
| |
2976 |
| |
2977 srvc = mwPlace_getService(place); |
| |
2978 session = mwService_getSession(MW_SERVICE(srvc)); |
| |
2979 pd = mwSession_getClientData(session); |
| |
2980 gc = pd->gc; |
| |
2981 |
| |
2982 gconf = mwPlace_getClientData(place); |
| |
2983 g_return_if_fail(gconf != NULL); |
| |
2984 |
| |
2985 gaim_conv_chat_add_user(GAIM_CONV_CHAT(gconf), peer->user, |
| |
2986 NULL, GAIM_CBFLAGS_NONE, TRUE); |
| |
2987 } |
| |
2988 |
| |
2989 |
| |
2990 static void mw_place_peerParted(struct mwPlace *place, |
| |
2991 const struct mwIdBlock *peer) { |
| |
2992 struct mwServicePlace *srvc; |
| |
2993 struct mwSession *session; |
| |
2994 struct mwGaimPluginData *pd; |
| |
2995 GaimConnection *gc; |
| |
2996 GaimConversation *gconf; |
| |
2997 |
| |
2998 const char *n = mwPlace_getName(place); |
| |
2999 |
| |
3000 DEBUG_INFO("%s left place %s\n", NSTR(peer->user), NSTR(n)); |
| |
3001 |
| |
3002 srvc = mwPlace_getService(place); |
| |
3003 session = mwService_getSession(MW_SERVICE(srvc)); |
| |
3004 pd = mwSession_getClientData(session); |
| |
3005 gc = pd->gc; |
| |
3006 |
| |
3007 gconf = mwPlace_getClientData(place); |
| |
3008 g_return_if_fail(gconf != NULL); |
| |
3009 |
| |
3010 gaim_conv_chat_remove_user(GAIM_CONV_CHAT(gconf), peer->user, NULL); |
| |
3011 } |
| |
3012 |
| |
3013 |
| |
3014 static void mw_place_peerSetAttribute(struct mwPlace *place, |
| |
3015 const struct mwIdBlock *peer, |
| |
3016 guint32 attr, struct mwOpaque *o) { |
| |
3017 ; |
| |
3018 } |
| |
3019 |
| |
3020 |
| |
3021 static void mw_place_peerUnsetAttribute(struct mwPlace *place, |
| |
3022 const struct mwIdBlock *peer, |
| |
3023 guint32 attr) { |
| |
3024 ; |
| |
3025 } |
| |
3026 |
| |
3027 |
| |
3028 static void mw_place_message(struct mwPlace *place, |
| |
3029 const struct mwIdBlock *who, |
| |
3030 const char *msg) { |
| |
3031 struct mwServicePlace *srvc; |
| |
3032 struct mwSession *session; |
| |
3033 struct mwGaimPluginData *pd; |
| |
3034 GaimConnection *gc; |
| |
3035 char *esc; |
| |
3036 |
| |
3037 if(! msg) return; |
| |
3038 |
| |
3039 srvc = mwPlace_getService(place); |
| |
3040 session = mwService_getSession(MW_SERVICE(srvc)); |
| |
3041 pd = mwSession_getClientData(session); |
| |
3042 gc = pd->gc; |
| |
3043 |
| |
3044 esc = g_markup_escape_text(msg, -1); |
| |
3045 serv_got_chat_in(gc, PLACE_TO_ID(place), who->user, 0, esc, time(NULL)); |
| |
3046 g_free(esc); |
| |
3047 } |
| |
3048 |
| |
3049 |
| |
3050 static void mw_place_clear(struct mwServicePlace *srvc) { |
| |
3051 ; |
| |
3052 } |
| |
3053 |
| |
3054 |
| |
3055 static struct mwPlaceHandler mw_place_handler = { |
| |
3056 .opened = mw_place_opened, |
| |
3057 .closed = mw_place_closed, |
| |
3058 .peerJoined = mw_place_peerJoined, |
| |
3059 .peerParted = mw_place_peerParted, |
| |
3060 .peerSetAttribute = mw_place_peerSetAttribute, |
| |
3061 .peerUnsetAttribute = mw_place_peerUnsetAttribute, |
| |
3062 .message = mw_place_message, |
| |
3063 .clear = mw_place_clear, |
| |
3064 }; |
| |
3065 |
| |
3066 |
| |
3067 static struct mwServicePlace *mw_srvc_place_new(struct mwSession *s) { |
| |
3068 struct mwServicePlace *srvc; |
| |
3069 srvc = mwServicePlace_new(s, &mw_place_handler); |
| |
3070 return srvc; |
| |
3071 } |
| |
3072 |
| |
3073 |
| |
3074 static struct mwServiceResolve *mw_srvc_resolve_new(struct mwSession *s) { |
| |
3075 struct mwServiceResolve *srvc; |
| |
3076 srvc = mwServiceResolve_new(s); |
| |
3077 return srvc; |
| |
3078 } |
| |
3079 |
| |
3080 |
| |
3081 static struct mwServiceStorage *mw_srvc_store_new(struct mwSession *s) { |
| |
3082 struct mwServiceStorage *srvc; |
| |
3083 srvc = mwServiceStorage_new(s); |
| |
3084 return srvc; |
| |
3085 } |
| |
3086 |
| |
3087 |
| |
3088 /** allocate and associate a mwGaimPluginData with a GaimConnection */ |
| |
3089 static struct mwGaimPluginData *mwGaimPluginData_new(GaimConnection *gc) { |
| |
3090 struct mwGaimPluginData *pd; |
| |
3091 |
| |
3092 g_return_val_if_fail(gc != NULL, NULL); |
| |
3093 |
| |
3094 pd = g_new0(struct mwGaimPluginData, 1); |
| |
3095 pd->gc = gc; |
| |
3096 pd->session = mwSession_new(&mw_session_handler); |
| |
3097 pd->srvc_aware = mw_srvc_aware_new(pd->session); |
| |
3098 pd->srvc_conf = mw_srvc_conf_new(pd->session); |
| |
3099 pd->srvc_ft = mw_srvc_ft_new(pd->session); |
| |
3100 pd->srvc_im = mw_srvc_im_new(pd->session); |
| |
3101 pd->srvc_place = mw_srvc_place_new(pd->session); |
| |
3102 pd->srvc_resolve = mw_srvc_resolve_new(pd->session); |
| |
3103 pd->srvc_store = mw_srvc_store_new(pd->session); |
| |
3104 pd->group_list_map = g_hash_table_new(g_direct_hash, g_direct_equal); |
| |
3105 pd->sock_buf = gaim_circ_buffer_new(0); |
| |
3106 |
| |
3107 mwSession_addService(pd->session, MW_SERVICE(pd->srvc_aware)); |
| |
3108 mwSession_addService(pd->session, MW_SERVICE(pd->srvc_conf)); |
| |
3109 mwSession_addService(pd->session, MW_SERVICE(pd->srvc_ft)); |
| |
3110 mwSession_addService(pd->session, MW_SERVICE(pd->srvc_im)); |
| |
3111 mwSession_addService(pd->session, MW_SERVICE(pd->srvc_place)); |
| |
3112 mwSession_addService(pd->session, MW_SERVICE(pd->srvc_resolve)); |
| |
3113 mwSession_addService(pd->session, MW_SERVICE(pd->srvc_store)); |
| |
3114 |
| |
3115 mwSession_addCipher(pd->session, mwCipher_new_RC2_40(pd->session)); |
| |
3116 mwSession_addCipher(pd->session, mwCipher_new_RC2_128(pd->session)); |
| |
3117 |
| |
3118 mwSession_setClientData(pd->session, pd, NULL); |
| |
3119 gc->proto_data = pd; |
| |
3120 |
| |
3121 return pd; |
| |
3122 } |
| |
3123 |
| |
3124 |
| |
3125 static void mwGaimPluginData_free(struct mwGaimPluginData *pd) { |
| |
3126 g_return_if_fail(pd != NULL); |
| |
3127 |
| |
3128 pd->gc->proto_data = NULL; |
| |
3129 |
| |
3130 mwSession_removeService(pd->session, mwService_AWARE); |
| |
3131 mwSession_removeService(pd->session, mwService_CONFERENCE); |
| |
3132 mwSession_removeService(pd->session, mwService_FILE_TRANSFER); |
| |
3133 mwSession_removeService(pd->session, mwService_IM); |
| |
3134 mwSession_removeService(pd->session, mwService_PLACE); |
| |
3135 mwSession_removeService(pd->session, mwService_RESOLVE); |
| |
3136 mwSession_removeService(pd->session, mwService_STORAGE); |
| |
3137 |
| |
3138 mwService_free(MW_SERVICE(pd->srvc_aware)); |
| |
3139 mwService_free(MW_SERVICE(pd->srvc_conf)); |
| |
3140 mwService_free(MW_SERVICE(pd->srvc_ft)); |
| |
3141 mwService_free(MW_SERVICE(pd->srvc_im)); |
| |
3142 mwService_free(MW_SERVICE(pd->srvc_place)); |
| |
3143 mwService_free(MW_SERVICE(pd->srvc_resolve)); |
| |
3144 mwService_free(MW_SERVICE(pd->srvc_store)); |
| |
3145 |
| |
3146 mwCipher_free(mwSession_getCipher(pd->session, mwCipher_RC2_40)); |
| |
3147 mwCipher_free(mwSession_getCipher(pd->session, mwCipher_RC2_128)); |
| |
3148 |
| |
3149 mwSession_free(pd->session); |
| |
3150 |
| |
3151 g_hash_table_destroy(pd->group_list_map); |
| |
3152 gaim_circ_buffer_destroy(pd->sock_buf); |
| |
3153 |
| |
3154 g_free(pd); |
| |
3155 } |
| |
3156 |
| |
3157 |
| |
3158 static const char *mw_prpl_list_icon(GaimAccount *a, GaimBuddy *b) { |
| |
3159 /* my little green dude is a chopped up version of the aim running |
| |
3160 guy. First, cut off the head and store someplace safe. Then, |
| |
3161 take the left-half side of the body and throw it away. Make a |
| |
3162 copy of the remaining body, and flip it horizontally. Now attach |
| |
3163 the two pieces into an X shape, and drop the head back on the |
| |
3164 top, being careful to center it. Then, just change the color |
| |
3165 saturation to bring the red down a bit, and voila! */ |
| |
3166 |
| |
3167 /* then, throw all of that away and use sodipodi to make a new |
| |
3168 icon. You know, LIKE A REAL MAN. */ |
| |
3169 |
| |
3170 return "meanwhile"; |
| |
3171 } |
| |
3172 |
| |
3173 |
| |
3174 static void mw_prpl_list_emblems(GaimBuddy *b, |
| |
3175 const char **se, const char **sw, |
| |
3176 const char **nw, const char **ne) { |
| |
3177 |
| |
3178 /* speaking of custom icons, the external icon here is an ugly |
| |
3179 little example of what happens when I use Gimp */ |
| |
3180 |
| |
3181 GaimPresence *presence; |
| |
3182 GaimStatus *status; |
| |
3183 const char *status_id; |
| |
3184 |
| |
3185 presence = gaim_buddy_get_presence(b); |
| |
3186 status = gaim_presence_get_active_status(presence); |
| |
3187 status_id = gaim_status_get_id(status); |
| |
3188 |
| |
3189 if(! GAIM_BUDDY_IS_ONLINE(b)) { |
| |
3190 *se = "offline"; |
| |
3191 } else if(!strcmp(status_id, MW_STATE_AWAY)) { |
| |
3192 *se = "away"; |
| |
3193 } else if(!strcmp(status_id, MW_STATE_BUSY)) { |
| |
3194 *se = "dnd"; |
| |
3195 } |
| |
3196 |
| |
3197 if(buddy_is_external(b)) { |
| |
3198 /* best assignment ever */ |
| |
3199 *(*se?sw:se) = "external"; |
| |
3200 } |
| |
3201 } |
| |
3202 |
| |
3203 |
| |
3204 static char *mw_prpl_status_text(GaimBuddy *b) { |
| |
3205 GaimConnection *gc; |
| |
3206 struct mwGaimPluginData *pd; |
| |
3207 struct mwAwareIdBlock t = { mwAware_USER, b->name, NULL }; |
| |
3208 const char *ret; |
| |
3209 |
| |
3210 gc = b->account->gc; |
| |
3211 pd = gc->proto_data; |
| |
3212 |
| |
3213 ret = mwServiceAware_getText(pd->srvc_aware, &t); |
| |
3214 return ret? g_markup_escape_text(ret, -1): NULL; |
| |
3215 } |
| |
3216 |
| |
3217 |
| |
3218 static const char *status_text(GaimBuddy *b) { |
| |
3219 GaimPresence *presence; |
| |
3220 GaimStatus *status; |
| |
3221 |
| |
3222 presence = gaim_buddy_get_presence(b); |
| |
3223 status = gaim_presence_get_active_status(presence); |
| |
3224 |
| |
3225 return gaim_status_get_name(status); |
| |
3226 } |
| |
3227 |
| |
3228 |
| |
3229 static gboolean user_supports(struct mwServiceAware *srvc, |
| |
3230 const char *who, guint32 feature) { |
| |
3231 |
| |
3232 const struct mwAwareAttribute *attr; |
| |
3233 struct mwAwareIdBlock idb = { mwAware_USER, (char *) who, NULL }; |
| |
3234 |
| |
3235 attr = mwServiceAware_getAttribute(srvc, &idb, feature); |
| |
3236 return (attr != NULL) && mwAwareAttribute_asBoolean(attr); |
| |
3237 } |
| |
3238 |
| |
3239 |
| |
3240 static char *user_supports_text(struct mwServiceAware *srvc, const char *who) { |
| |
3241 const char *feat[] = {NULL, NULL, NULL, NULL, NULL}; |
| |
3242 const char **f = feat; |
| |
3243 |
| |
3244 if(user_supports(srvc, who, mwAttribute_AV_PREFS_SET)) { |
| |
3245 gboolean mic, speak, video; |
| |
3246 |
| |
3247 mic = user_supports(srvc, who, mwAttribute_MICROPHONE); |
| |
3248 speak = user_supports(srvc, who, mwAttribute_SPEAKERS); |
| |
3249 video = user_supports(srvc, who, mwAttribute_VIDEO_CAMERA); |
| |
3250 |
| |
3251 if(mic) *f++ = _("Microphone"); |
| |
3252 if(speak) *f++ = _("Speakers"); |
| |
3253 if(video) *f++ = _("Video Camera"); |
| |
3254 } |
| |
3255 |
| |
3256 if(user_supports(srvc, who, mwAttribute_FILE_TRANSFER)) |
| |
3257 *f++ = _("File Transfer"); |
| |
3258 |
| |
3259 return (*feat)? g_strjoinv(", ", (char **)feat): NULL; |
| |
3260 /* jenni loves siege */ |
| |
3261 } |
| |
3262 |
| |
3263 |
| |
3264 static void mw_prpl_tooltip_text(GaimBuddy *b, GaimNotifyUserInfo *user_info, gboolean full) { |
| |
3265 GaimConnection *gc; |
| |
3266 struct mwGaimPluginData *pd; |
| |
3267 struct mwAwareIdBlock idb = { mwAware_USER, b->name, NULL }; |
| |
3268 |
| |
3269 const char *message; |
| |
3270 const char *status; |
| |
3271 char *tmp; |
| |
3272 |
| |
3273 gc = b->account->gc; |
| |
3274 pd = gc->proto_data; |
| |
3275 |
| |
3276 message = mwServiceAware_getText(pd->srvc_aware, &idb); |
| |
3277 status = status_text(b); |
| |
3278 |
| |
3279 if(message != NULL && gaim_utf8_strcasecmp(status, message)) { |
| |
3280 tmp = g_markup_escape_text(message, -1); |
| |
3281 gaim_notify_user_info_add_pair(user_info, status, tmp); |
| |
3282 g_free(tmp); |
| |
3283 |
| |
3284 } else { |
| |
3285 gaim_notify_user_info_add_pair(user_info, _("Status"), status); |
| |
3286 } |
| |
3287 |
| |
3288 if(full) { |
| |
3289 tmp = user_supports_text(pd->srvc_aware, b->name); |
| |
3290 if(tmp) { |
| |
3291 gaim_notify_user_info_add_pair(user_info, _("Supports"), tmp); |
| |
3292 g_free(tmp); |
| |
3293 } |
| |
3294 |
| |
3295 if(buddy_is_external(b)) { |
| |
3296 gaim_notify_user_info_add_pair(user_info, NULL, _("External User")); |
| |
3297 } |
| |
3298 } |
| |
3299 } |
| |
3300 |
| |
3301 |
| |
3302 static GList *mw_prpl_status_types(GaimAccount *acct) { |
| |
3303 GList *types = NULL; |
| |
3304 GaimStatusType *type; |
| |
3305 |
| |
3306 type = gaim_status_type_new(GAIM_STATUS_AVAILABLE, MW_STATE_ACTIVE, |
| |
3307 NULL, TRUE); |
| |
3308 gaim_status_type_add_attr(type, MW_STATE_MESSAGE, _("Message"), |
| |
3309 gaim_value_new(GAIM_TYPE_STRING)); |
| |
3310 types = g_list_append(types, type); |
| |
3311 |
| |
3312 type = gaim_status_type_new(GAIM_STATUS_AWAY, MW_STATE_AWAY, |
| |
3313 NULL, TRUE); |
| |
3314 gaim_status_type_add_attr(type, MW_STATE_MESSAGE, _("Message"), |
| |
3315 gaim_value_new(GAIM_TYPE_STRING)); |
| |
3316 types = g_list_append(types, type); |
| |
3317 |
| |
3318 type = gaim_status_type_new(GAIM_STATUS_UNAVAILABLE, MW_STATE_BUSY, |
| |
3319 _("Do Not Disturb"), TRUE); |
| |
3320 gaim_status_type_add_attr(type, MW_STATE_MESSAGE, _("Message"), |
| |
3321 gaim_value_new(GAIM_TYPE_STRING)); |
| |
3322 types = g_list_append(types, type); |
| |
3323 |
| |
3324 type = gaim_status_type_new(GAIM_STATUS_OFFLINE, MW_STATE_OFFLINE, |
| |
3325 NULL, TRUE); |
| |
3326 types = g_list_append(types, type); |
| |
3327 |
| |
3328 return types; |
| |
3329 } |
| |
3330 |
| |
3331 |
| |
3332 static void conf_create_prompt_cancel(GaimBuddy *buddy, |
| |
3333 GaimRequestFields *fields) { |
| |
3334 ; /* nothing to do */ |
| |
3335 } |
| |
3336 |
| |
3337 |
| |
3338 static void conf_create_prompt_join(GaimBuddy *buddy, |
| |
3339 GaimRequestFields *fields) { |
| |
3340 GaimAccount *acct; |
| |
3341 GaimConnection *gc; |
| |
3342 struct mwGaimPluginData *pd; |
| |
3343 struct mwServiceConference *srvc; |
| |
3344 |
| |
3345 GaimRequestField *f; |
| |
3346 |
| |
3347 const char *topic, *invite; |
| |
3348 struct mwConference *conf; |
| |
3349 struct mwIdBlock idb = { NULL, NULL }; |
| |
3350 |
| |
3351 acct = buddy->account; |
| |
3352 gc = gaim_account_get_connection(acct); |
| |
3353 pd = gc->proto_data; |
| |
3354 srvc = pd->srvc_conf; |
| |
3355 |
| |
3356 f = gaim_request_fields_get_field(fields, CHAT_KEY_TOPIC); |
| |
3357 topic = gaim_request_field_string_get_value(f); |
| |
3358 |
| |
3359 f = gaim_request_fields_get_field(fields, CHAT_KEY_INVITE); |
| |
3360 invite = gaim_request_field_string_get_value(f); |
| |
3361 |
| |
3362 conf = mwConference_new(srvc, topic); |
| |
3363 mwConference_open(conf); |
| |
3364 |
| |
3365 idb.user = buddy->name; |
| |
3366 mwConference_invite(conf, &idb, invite); |
| |
3367 } |
| |
3368 |
| |
3369 |
| |
3370 static void blist_menu_conf_create(GaimBuddy *buddy, const char *msg) { |
| |
3371 |
| |
3372 GaimRequestFields *fields; |
| |
3373 GaimRequestFieldGroup *g; |
| |
3374 GaimRequestField *f; |
| |
3375 |
| |
3376 GaimAccount *acct; |
| |
3377 GaimConnection *gc; |
| |
3378 |
| |
3379 const char *msgA; |
| |
3380 const char *msgB; |
| |
3381 char *msg1; |
| |
3382 |
| |
3383 g_return_if_fail(buddy != NULL); |
| |
3384 |
| |
3385 acct = buddy->account; |
| |
3386 g_return_if_fail(acct != NULL); |
| |
3387 |
| |
3388 gc = gaim_account_get_connection(acct); |
| |
3389 g_return_if_fail(gc != NULL); |
| |
3390 |
| |
3391 fields = gaim_request_fields_new(); |
| |
3392 |
| |
3393 g = gaim_request_field_group_new(NULL); |
| |
3394 gaim_request_fields_add_group(fields, g); |
| |
3395 |
| |
3396 f = gaim_request_field_string_new(CHAT_KEY_TOPIC, _("Topic"), NULL, FALSE); |
| |
3397 gaim_request_field_group_add_field(g, f); |
| |
3398 |
| |
3399 f = gaim_request_field_string_new(CHAT_KEY_INVITE, _("Message"), msg, FALSE); |
| |
3400 gaim_request_field_group_add_field(g, f); |
| |
3401 |
| |
3402 msgA = _("Create conference with user"); |
| |
3403 msgB = _("Please enter a topic for the new conference, and an invitation" |
| |
3404 " message to be sent to %s"); |
| |
3405 msg1 = g_strdup_printf(msgB, buddy->name); |
| |
3406 |
| |
3407 gaim_request_fields(gc, _("New Conference"), |
| |
3408 msgA, msg1, fields, |
| |
3409 _("Create"), G_CALLBACK(conf_create_prompt_join), |
| |
3410 _("Cancel"), G_CALLBACK(conf_create_prompt_cancel), |
| |
3411 buddy); |
| |
3412 g_free(msg1); |
| |
3413 } |
| |
3414 |
| |
3415 |
| |
3416 static void conf_select_prompt_cancel(GaimBuddy *buddy, |
| |
3417 GaimRequestFields *fields) { |
| |
3418 ; |
| |
3419 } |
| |
3420 |
| |
3421 |
| |
3422 static void conf_select_prompt_invite(GaimBuddy *buddy, |
| |
3423 GaimRequestFields *fields) { |
| |
3424 GaimRequestField *f; |
| |
3425 const GList *l; |
| |
3426 const char *msg; |
| |
3427 |
| |
3428 f = gaim_request_fields_get_field(fields, CHAT_KEY_INVITE); |
| |
3429 msg = gaim_request_field_string_get_value(f); |
| |
3430 |
| |
3431 f = gaim_request_fields_get_field(fields, "conf"); |
| |
3432 l = gaim_request_field_list_get_selected(f); |
| |
3433 |
| |
3434 if(l) { |
| |
3435 gpointer d = gaim_request_field_list_get_data(f, l->data); |
| |
3436 |
| |
3437 if(GPOINTER_TO_INT(d) == 0x01) { |
| |
3438 blist_menu_conf_create(buddy, msg); |
| |
3439 |
| |
3440 } else { |
| |
3441 struct mwIdBlock idb = { buddy->name, NULL }; |
| |
3442 mwConference_invite(d, &idb, msg); |
| |
3443 } |
| |
3444 } |
| |
3445 } |
| |
3446 |
| |
3447 |
| |
3448 static void blist_menu_conf_list(GaimBuddy *buddy, |
| |
3449 GList *confs) { |
| |
3450 |
| |
3451 GaimRequestFields *fields; |
| |
3452 GaimRequestFieldGroup *g; |
| |
3453 GaimRequestField *f; |
| |
3454 |
| |
3455 GaimAccount *acct; |
| |
3456 GaimConnection *gc; |
| |
3457 |
| |
3458 const char *msgA; |
| |
3459 const char *msgB; |
| |
3460 char *msg; |
| |
3461 |
| |
3462 acct = buddy->account; |
| |
3463 g_return_if_fail(acct != NULL); |
| |
3464 |
| |
3465 gc = gaim_account_get_connection(acct); |
| |
3466 g_return_if_fail(gc != NULL); |
| |
3467 |
| |
3468 fields = gaim_request_fields_new(); |
| |
3469 |
| |
3470 g = gaim_request_field_group_new(NULL); |
| |
3471 gaim_request_fields_add_group(fields, g); |
| |
3472 |
| |
3473 f = gaim_request_field_list_new("conf", _("Available Conferences")); |
| |
3474 gaim_request_field_list_set_multi_select(f, FALSE); |
| |
3475 for(; confs; confs = confs->next) { |
| |
3476 struct mwConference *c = confs->data; |
| |
3477 gaim_request_field_list_add(f, mwConference_getTitle(c), c); |
| |
3478 } |
| |
3479 gaim_request_field_list_add(f, _("Create New Conference..."), |
| |
3480 GINT_TO_POINTER(0x01)); |
| |
3481 gaim_request_field_group_add_field(g, f); |
| |
3482 |
| |
3483 f = gaim_request_field_string_new(CHAT_KEY_INVITE, "Message", NULL, FALSE); |
| |
3484 gaim_request_field_group_add_field(g, f); |
| |
3485 |
| |
3486 msgA = _("Invite user to a conference"); |
| |
3487 msgB = _("Select a conference from the list below to send an invite to" |
| |
3488 " user %s. Select \"Create New Conference\" if you'd like to" |
| |
3489 " create a new conference to invite this user to."); |
| |
3490 msg = g_strdup_printf(msgB, buddy->name); |
| |
3491 |
| |
3492 gaim_request_fields(gc, _("Invite to Conference"), |
| |
3493 msgA, msg, fields, |
| |
3494 _("Invite"), G_CALLBACK(conf_select_prompt_invite), |
| |
3495 _("Cancel"), G_CALLBACK(conf_select_prompt_cancel), |
| |
3496 buddy); |
| |
3497 g_free(msg); |
| |
3498 } |
| |
3499 |
| |
3500 |
| |
3501 static void blist_menu_conf(GaimBlistNode *node, gpointer data) { |
| |
3502 GaimBuddy *buddy = (GaimBuddy *) node; |
| |
3503 GaimAccount *acct; |
| |
3504 GaimConnection *gc; |
| |
3505 struct mwGaimPluginData *pd; |
| |
3506 GList *l; |
| |
3507 |
| |
3508 g_return_if_fail(node != NULL); |
| |
3509 g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node)); |
| |
3510 |
| |
3511 acct = buddy->account; |
| |
3512 g_return_if_fail(acct != NULL); |
| |
3513 |
| |
3514 gc = gaim_account_get_connection(acct); |
| |
3515 g_return_if_fail(gc != NULL); |
| |
3516 |
| |
3517 pd = gc->proto_data; |
| |
3518 g_return_if_fail(pd != NULL); |
| |
3519 |
| |
3520 /* |
| |
3521 - get a list of all conferences on this session |
| |
3522 - if none, prompt to create one, and invite buddy to it |
| |
3523 - else, prompt to select a conference or create one |
| |
3524 */ |
| |
3525 |
| |
3526 l = mwServiceConference_getConferences(pd->srvc_conf); |
| |
3527 if(l) { |
| |
3528 blist_menu_conf_list(buddy, l); |
| |
3529 g_list_free(l); |
| |
3530 |
| |
3531 } else { |
| |
3532 blist_menu_conf_create(buddy, NULL); |
| |
3533 } |
| |
3534 } |
| |
3535 |
| |
3536 |
| |
3537 #if 0 |
| |
3538 static void blist_menu_announce(GaimBlistNode *node, gpointer data) { |
| |
3539 GaimBuddy *buddy = (GaimBuddy *) node; |
| |
3540 GaimAccount *acct; |
| |
3541 GaimConnection *gc; |
| |
3542 struct mwGaimPluginData *pd; |
| |
3543 struct mwSession *session; |
| |
3544 char *rcpt_name; |
| |
3545 GList *rcpt; |
| |
3546 |
| |
3547 g_return_if_fail(node != NULL); |
| |
3548 g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node)); |
| |
3549 |
| |
3550 acct = buddy->account; |
| |
3551 g_return_if_fail(acct != NULL); |
| |
3552 |
| |
3553 gc = gaim_account_get_connection(acct); |
| |
3554 g_return_if_fail(gc != NULL); |
| |
3555 |
| |
3556 pd = gc->proto_data; |
| |
3557 g_return_if_fail(pd != NULL); |
| |
3558 |
| |
3559 rcpt_name = g_strdup_printf("@U %s", buddy->name); |
| |
3560 rcpt = g_list_prepend(NULL, rcpt_name); |
| |
3561 |
| |
3562 session = pd->session; |
| |
3563 mwSession_sendAnnounce(session, FALSE, |
| |
3564 "This is a TEST announcement. Please ignore.", |
| |
3565 rcpt); |
| |
3566 |
| |
3567 g_list_free(rcpt); |
| |
3568 g_free(rcpt_name); |
| |
3569 } |
| |
3570 #endif |
| |
3571 |
| |
3572 |
| |
3573 static GList *mw_prpl_blist_node_menu(GaimBlistNode *node) { |
| |
3574 GList *l = NULL; |
| |
3575 GaimMenuAction *act; |
| |
3576 |
| |
3577 if(! GAIM_BLIST_NODE_IS_BUDDY(node)) |
| |
3578 return l; |
| |
3579 |
| |
3580 l = g_list_append(l, NULL); |
| |
3581 |
| |
3582 act = gaim_menu_action_new(_("Invite to Conference..."), |
| |
3583 GAIM_CALLBACK(blist_menu_conf), NULL, NULL); |
| |
3584 l = g_list_append(l, act); |
| |
3585 |
| |
3586 #if 0 |
| |
3587 act = gaim_menu_action_new(_("Send TEST Announcement"), |
| |
3588 GAIM_CALLBACK(blist_menu_announce), NULL, NULL); |
| |
3589 l = g_list_append(l, act); |
| |
3590 #endif |
| |
3591 |
| |
3592 /** note: this never gets called for a GaimGroup, have to use the |
| |
3593 blist-node-extended-menu signal for that. The function |
| |
3594 blist_node_menu_cb is assigned to this signal in the function |
| |
3595 services_starting */ |
| |
3596 |
| |
3597 return l; |
| |
3598 } |
| |
3599 |
| |
3600 |
| |
3601 static GList *mw_prpl_chat_info(GaimConnection *gc) { |
| |
3602 GList *l = NULL; |
| |
3603 struct proto_chat_entry *pce; |
| |
3604 |
| |
3605 pce = g_new0(struct proto_chat_entry, 1); |
| |
3606 pce->label = _("Topic:"); |
| |
3607 pce->identifier = CHAT_KEY_TOPIC; |
| |
3608 l = g_list_append(l, pce); |
| |
3609 |
| |
3610 return l; |
| |
3611 } |
| |
3612 |
| |
3613 |
| |
3614 static GHashTable *mw_prpl_chat_info_defaults(GaimConnection *gc, |
| |
3615 const char *name) { |
| |
3616 GHashTable *table; |
| |
3617 |
| |
3618 g_return_val_if_fail(gc != NULL, NULL); |
| |
3619 |
| |
3620 table = g_hash_table_new_full(g_str_hash, g_str_equal, |
| |
3621 NULL, g_free); |
| |
3622 |
| |
3623 g_hash_table_insert(table, CHAT_KEY_NAME, g_strdup(name)); |
| |
3624 g_hash_table_insert(table, CHAT_KEY_INVITE, NULL); |
| |
3625 |
| |
3626 return table; |
| |
3627 } |
| |
3628 |
| |
3629 |
| |
3630 static void mw_prpl_login(GaimAccount *acct); |
| |
3631 |
| |
3632 |
| |
3633 static void prompt_host_cancel_cb(GaimConnection *gc) { |
| |
3634 gaim_connection_error(gc, _("No Sametime Community Server specified")); |
| |
3635 } |
| |
3636 |
| |
3637 |
| |
3638 static void prompt_host_ok_cb(GaimConnection *gc, const char *host) { |
| |
3639 if(host && *host) { |
| |
3640 GaimAccount *acct = gaim_connection_get_account(gc); |
| |
3641 gaim_account_set_string(acct, MW_KEY_HOST, host); |
| |
3642 mw_prpl_login(acct); |
| |
3643 |
| |
3644 } else { |
| |
3645 prompt_host_cancel_cb(gc); |
| |
3646 } |
| |
3647 } |
| |
3648 |
| |
3649 |
| |
3650 static void prompt_host(GaimConnection *gc) { |
| |
3651 GaimAccount *acct; |
| |
3652 const char *msgA; |
| |
3653 char *msg; |
| |
3654 |
| |
3655 acct = gaim_connection_get_account(gc); |
| |
3656 msgA = _("No host or IP address has been configured for the" |
| |
3657 " Meanwhile account %s. Please enter one below to" |
| |
3658 " continue logging in."); |
| |
3659 msg = g_strdup_printf(msgA, NSTR(gaim_account_get_username(acct))); |
| |
3660 |
| |
3661 gaim_request_input(gc, _("Meanwhile Connection Setup"), |
| |
3662 _("No Sametime Community Server Specified"), msg, |
| |
3663 MW_PLUGIN_DEFAULT_HOST, FALSE, FALSE, NULL, |
| |
3664 _("Connect"), G_CALLBACK(prompt_host_ok_cb), |
| |
3665 _("Cancel"), G_CALLBACK(prompt_host_cancel_cb), |
| |
3666 gc); |
| |
3667 |
| |
3668 g_free(msg); |
| |
3669 } |
| |
3670 |
| |
3671 |
| |
3672 static void mw_prpl_login(GaimAccount *account) { |
| |
3673 GaimConnection *gc; |
| |
3674 struct mwGaimPluginData *pd; |
| |
3675 |
| |
3676 char *user, *pass, *host; |
| |
3677 guint port; |
| |
3678 |
| |
3679 gc = gaim_account_get_connection(account); |
| |
3680 pd = mwGaimPluginData_new(gc); |
| |
3681 |
| |
3682 /* while we do support images, the default is to not offer it */ |
| |
3683 gc->flags |= GAIM_CONNECTION_NO_IMAGES; |
| |
3684 |
| |
3685 user = g_strdup(gaim_account_get_username(account)); |
| |
3686 pass = g_strdup(gaim_account_get_password(account)); |
| |
3687 |
| |
3688 host = strrchr(user, ':'); |
| |
3689 if(host) { |
| |
3690 /* annoying user split from 1.2.0, need to undo it */ |
| |
3691 *host++ = '\0'; |
| |
3692 gaim_account_set_string(account, MW_KEY_HOST, host); |
| |
3693 gaim_account_set_username(account, user); |
| |
3694 |
| |
3695 } else { |
| |
3696 host = (char *) gaim_account_get_string(account, MW_KEY_HOST, |
| |
3697 MW_PLUGIN_DEFAULT_HOST); |
| |
3698 } |
| |
3699 |
| |
3700 if(! host || ! *host) { |
| |
3701 /* somehow, we don't have a host to connect to. Well, we need one |
| |
3702 to actually continue, so let's ask the user directly. */ |
| |
3703 prompt_host(gc); |
| |
3704 return; |
| |
3705 } |
| |
3706 |
| |
3707 port = gaim_account_get_int(account, MW_KEY_PORT, MW_PLUGIN_DEFAULT_PORT); |
| |
3708 |
| |
3709 DEBUG_INFO("user: '%s'\n", user); |
| |
3710 DEBUG_INFO("host: '%s'\n", host); |
| |
3711 DEBUG_INFO("port: %u\n", port); |
| |
3712 |
| |
3713 mwSession_setProperty(pd->session, mwSession_NO_SECRET, |
| |
3714 (char *) no_secret, NULL); |
| |
3715 mwSession_setProperty(pd->session, mwSession_AUTH_USER_ID, user, g_free); |
| |
3716 mwSession_setProperty(pd->session, mwSession_AUTH_PASSWORD, pass, g_free); |
| |
3717 |
| |
3718 if(gaim_account_get_bool(account, MW_KEY_FAKE_IT, FALSE)) { |
| |
3719 guint client, major, minor; |
| |
3720 |
| |
3721 /* if we're faking the login, let's also fake the version we're |
| |
3722 reporting. Let's also allow the actual values to be specified */ |
| |
3723 |
| |
3724 client = gaim_account_get_int(account, MW_KEY_CLIENT, mwLogin_BINARY); |
| |
3725 major = gaim_account_get_int(account, MW_KEY_MAJOR, 0x001e); |
| |
3726 minor = gaim_account_get_int(account, MW_KEY_MINOR, 0x001d); |
| |
3727 |
| |
3728 DEBUG_INFO("client id: 0x%04x\n", client); |
| |
3729 DEBUG_INFO("client major: 0x%04x\n", major); |
| |
3730 DEBUG_INFO("client minor: 0x%04x\n", minor); |
| |
3731 |
| |
3732 mwSession_setProperty(pd->session, mwSession_CLIENT_TYPE_ID, |
| |
3733 GUINT_TO_POINTER(client), NULL); |
| |
3734 |
| |
3735 mwSession_setProperty(pd->session, mwSession_CLIENT_VER_MAJOR, |
| |
3736 GUINT_TO_POINTER(major), NULL); |
| |
3737 |
| |
3738 mwSession_setProperty(pd->session, mwSession_CLIENT_VER_MINOR, |
| |
3739 GUINT_TO_POINTER(minor), NULL); |
| |
3740 } |
| |
3741 |
| |
3742 gaim_connection_update_progress(gc, _("Connecting"), 1, MW_CONNECT_STEPS); |
| |
3743 |
| |
3744 if (gaim_proxy_connect(gc, account, host, port, connect_cb, pd) == NULL) { |
| |
3745 gaim_connection_error(gc, _("Unable to connect to host")); |
| |
3746 } |
| |
3747 } |
| |
3748 |
| |
3749 |
| |
3750 static void mw_prpl_close(GaimConnection *gc) { |
| |
3751 struct mwGaimPluginData *pd; |
| |
3752 |
| |
3753 g_return_if_fail(gc != NULL); |
| |
3754 |
| |
3755 pd = gc->proto_data; |
| |
3756 g_return_if_fail(pd != NULL); |
| |
3757 |
| |
3758 /* get rid of the blist save timeout */ |
| |
3759 if(pd->save_event) { |
| |
3760 gaim_timeout_remove(pd->save_event); |
| |
3761 pd->save_event = 0; |
| |
3762 blist_store(pd); |
| |
3763 } |
| |
3764 |
| |
3765 /* stop the session */ |
| |
3766 mwSession_stop(pd->session, 0x00); |
| |
3767 |
| |
3768 /* no longer necessary */ |
| |
3769 gc->proto_data = NULL; |
| |
3770 |
| |
3771 /* stop watching the socket */ |
| |
3772 if(gc->inpa) { |
| |
3773 gaim_input_remove(gc->inpa); |
| |
3774 gc->inpa = 0; |
| |
3775 } |
| |
3776 |
| |
3777 /* clean up the rest */ |
| |
3778 mwGaimPluginData_free(pd); |
| |
3779 } |
| |
3780 |
| |
3781 |
| |
3782 static int mw_rand() { |
| |
3783 static int seed = 0; |
| |
3784 |
| |
3785 /* for diversity, not security. don't touch */ |
| |
3786 srand(time(NULL) ^ seed); |
| |
3787 seed = rand(); |
| |
3788 |
| |
3789 return seed; |
| |
3790 } |
| |
3791 |
| |
3792 |
| |
3793 /** generates a random-ish content id string */ |
| |
3794 static char *im_mime_content_id() { |
| |
3795 return g_strdup_printf("%03x@%05xmeanwhile", |
| |
3796 mw_rand() & 0xfff, mw_rand() & 0xfffff); |
| |
3797 } |
| |
3798 |
| |
3799 |
| |
3800 /** generates a multipart/related content type with a random-ish |
| |
3801 boundary value */ |
| |
3802 static char *im_mime_content_type() { |
| |
3803 return g_strdup_printf("multipart/related; boundary=related_MW%03x_%04x", |
| |
3804 mw_rand() & 0xfff, mw_rand() & 0xffff); |
| |
3805 } |
| |
3806 |
| |
3807 |
| |
3808 /** determine content type from extension. Not so happy about this, |
| |
3809 but I don't want to actually write image type detection */ |
| |
3810 static char *im_mime_img_content_type(GaimStoredImage *img) { |
| |
3811 const char *fn = gaim_imgstore_get_filename(img); |
| |
3812 const char *ct = NULL; |
| |
3813 |
| |
3814 ct = strrchr(fn, '.'); |
| |
3815 if(! ct) { |
| |
3816 ct = "image"; |
| |
3817 |
| |
3818 } else if(! strcmp(".png", ct)) { |
| |
3819 ct = "image/png"; |
| |
3820 |
| |
3821 } else if(! strcmp(".jpg", ct)) { |
| |
3822 ct = "image/jpeg"; |
| |
3823 |
| |
3824 } else if(! strcmp(".jpeg", ct)) { |
| |
3825 ct = "image/jpeg"; |
| |
3826 |
| |
3827 } else if(! strcmp(".gif", ct)) { |
| |
3828 ct = "image/gif"; |
| |
3829 |
| |
3830 } else { |
| |
3831 ct = "image"; |
| |
3832 } |
| |
3833 |
| |
3834 return g_strdup_printf("%s; name=\"%s\"", ct, fn); |
| |
3835 } |
| |
3836 |
| |
3837 |
| |
3838 static char *im_mime_img_content_disp(GaimStoredImage *img) { |
| |
3839 const char *fn = gaim_imgstore_get_filename(img); |
| |
3840 return g_strdup_printf("attachment; filename=\"%s\"", fn); |
| |
3841 } |
| |
3842 |
| |
3843 |
| |
3844 /** turn an IM with embedded images into a multi-part mime document */ |
| |
3845 static char *im_mime_convert(GaimConnection *gc, |
| |
3846 struct mwConversation *conv, |
| |
3847 const char *message) { |
| |
3848 GString *str; |
| |
3849 GaimMimeDocument *doc; |
| |
3850 GaimMimePart *part; |
| |
3851 |
| |
3852 GData *attr; |
| |
3853 char *tmp, *start, *end; |
| |
3854 |
| |
3855 str = g_string_new(NULL); |
| |
3856 |
| |
3857 doc = gaim_mime_document_new(); |
| |
3858 |
| |
3859 gaim_mime_document_set_field(doc, "Mime-Version", "1.0"); |
| |
3860 gaim_mime_document_set_field(doc, "Content-Disposition", "inline"); |
| |
3861 |
| |
3862 tmp = im_mime_content_type(); |
| |
3863 gaim_mime_document_set_field(doc, "Content-Type", tmp); |
| |
3864 g_free(tmp); |
| |
3865 |
| |
3866 tmp = (char *) message; |
| |
3867 while(*tmp && gaim_markup_find_tag("img", tmp, (const char **) &start, |
| |
3868 (const char **) &end, &attr)) { |
| |
3869 char *id; |
| |
3870 GaimStoredImage *img = NULL; |
| |
3871 |
| |
3872 gsize len = (start - tmp); |
| |
3873 |
| |
3874 /* append the in-between-tags text */ |
| |
3875 if(len) g_string_append_len(str, tmp, len); |
| |
3876 |
| |
3877 /* find the imgstore data by the id tag */ |
| |
3878 id = g_datalist_get_data(&attr, "id"); |
| |
3879 if(id && *id) |
| |
3880 img = gaim_imgstore_get(atoi(id)); |
| |
3881 |
| |
3882 if(img) { |
| |
3883 char *cid; |
| |
3884 gpointer data; |
| |
3885 size_t size; |
| |
3886 |
| |
3887 part = gaim_mime_part_new(doc); |
| |
3888 |
| |
3889 data = im_mime_img_content_disp(img); |
| |
3890 gaim_mime_part_set_field(part, "Content-Disposition", data); |
| |
3891 g_free(data); |
| |
3892 |
| |
3893 data = im_mime_img_content_type(img); |
| |
3894 gaim_mime_part_set_field(part, "Content-Type", data); |
| |
3895 g_free(data); |
| |
3896 |
| |
3897 cid = im_mime_content_id(); |
| |
3898 data = g_strdup_printf("<%s>", cid); |
| |
3899 gaim_mime_part_set_field(part, "Content-ID", data); |
| |
3900 g_free(data); |
| |
3901 |
| |
3902 gaim_mime_part_set_field(part, "Content-transfer-encoding", "base64"); |
| |
3903 |
| |
3904 /* obtain and base64 encode the image data, and put it in the |
| |
3905 mime part */ |
| |
3906 data = gaim_imgstore_get_data(img); |
| |
3907 size = gaim_imgstore_get_size(img); |
| |
3908 data = gaim_base64_encode(data, (gsize) size); |
| |
3909 gaim_mime_part_set_data(part, data); |
| |
3910 g_free(data); |
| |
3911 |
| |
3912 /* append the modified tag */ |
| |
3913 g_string_append_printf(str, "<img src=\"cid:%s\">", cid); |
| |
3914 g_free(cid); |
| |
3915 |
| |
3916 } else { |
| |
3917 /* append the literal image tag, since we couldn't find a |
| |
3918 relative imgstore object */ |
| |
3919 gsize len = (end - start) + 1; |
| |
3920 g_string_append_len(str, start, len); |
| |
3921 } |
| |
3922 |
| |
3923 g_datalist_clear(&attr); |
| |
3924 tmp = end + 1; |
| |
3925 } |
| |
3926 |
| |
3927 /* append left-overs */ |
| |
3928 g_string_append(str, tmp); |
| |
3929 |
| |
3930 /* add the text/html part */ |
| |
3931 part = gaim_mime_part_new(doc); |
| |
3932 gaim_mime_part_set_field(part, "Content-Disposition", "inline"); |
| |
3933 |
| |
3934 tmp = gaim_utf8_ncr_encode(str->str); |
| |
3935 gaim_mime_part_set_field(part, "Content-Type", "text/html"); |
| |
3936 gaim_mime_part_set_field(part, "Content-Transfer-Encoding", "7bit"); |
| |
3937 gaim_mime_part_set_data(part, tmp); |
| |
3938 g_free(tmp); |
| |
3939 |
| |
3940 g_string_free(str, TRUE); |
| |
3941 |
| |
3942 str = g_string_new(NULL); |
| |
3943 gaim_mime_document_write(doc, str); |
| |
3944 tmp = str->str; |
| |
3945 g_string_free(str, FALSE); |
| |
3946 |
| |
3947 return tmp; |
| |
3948 } |
| |
3949 |
| |
3950 |
| |
3951 static int mw_prpl_send_im(GaimConnection *gc, |
| |
3952 const char *name, |
| |
3953 const char *message, |
| |
3954 GaimMessageFlags flags) { |
| |
3955 |
| |
3956 struct mwGaimPluginData *pd; |
| |
3957 struct mwIdBlock who = { (char *) name, NULL }; |
| |
3958 struct mwConversation *conv; |
| |
3959 |
| |
3960 g_return_val_if_fail(gc != NULL, 0); |
| |
3961 pd = gc->proto_data; |
| |
3962 |
| |
3963 g_return_val_if_fail(pd != NULL, 0); |
| |
3964 |
| |
3965 conv = mwServiceIm_getConversation(pd->srvc_im, &who); |
| |
3966 |
| |
3967 /* this detection of features to determine how to send the message |
| |
3968 (plain, html, or mime) is flawed because the other end of the |
| |
3969 conversation could close their channel at any time, rendering any |
| |
3970 existing formatting in an outgoing message innapropriate. The end |
| |
3971 result is that it may be possible that the other side of the |
| |
3972 conversation will receive a plaintext message with html contents, |
| |
3973 which is bad. I'm not sure how to fix this correctly. */ |
| |
3974 |
| |
3975 if(strstr(message, "<img ") || strstr(message, "<IMG ")) |
| |
3976 flags |= GAIM_MESSAGE_IMAGES; |
| |
3977 |
| |
3978 if(mwConversation_isOpen(conv)) { |
| |
3979 char *tmp; |
| |
3980 int ret; |
| |
3981 |
| |
3982 if((flags & GAIM_MESSAGE_IMAGES) && |
| |
3983 mwConversation_supports(conv, mwImSend_MIME)) { |
| |
3984 /* send a MIME message */ |
| |
3985 |
| |
3986 tmp = im_mime_convert(gc, conv, message); |
| |
3987 ret = mwConversation_send(conv, mwImSend_MIME, tmp); |
| |
3988 g_free(tmp); |
| |
3989 |
| |
3990 } else if(mwConversation_supports(conv, mwImSend_HTML)) { |
| |
3991 /* send an HTML message */ |
| |
3992 |
| |
3993 char *ncr; |
| |
3994 ncr = gaim_utf8_ncr_encode(message); |
| |
3995 tmp = gaim_strdup_withhtml(ncr); |
| |
3996 g_free(ncr); |
| |
3997 |
| |
3998 ret = mwConversation_send(conv, mwImSend_HTML, tmp); |
| |
3999 g_free(tmp); |
| |
4000 |
| |
4001 } else { |
| |
4002 /* default to text */ |
| |
4003 tmp = gaim_markup_strip_html(message); |
| |
4004 ret = mwConversation_send(conv, mwImSend_PLAIN, tmp); |
| |
4005 g_free(tmp); |
| |
4006 } |
| |
4007 |
| |
4008 return !ret; |
| |
4009 |
| |
4010 } else { |
| |
4011 |
| |
4012 /* queue up the message safely as plain text */ |
| |
4013 char *tmp = gaim_markup_strip_html(message); |
| |
4014 convo_queue(conv, mwImSend_PLAIN, tmp); |
| |
4015 g_free(tmp); |
| |
4016 |
| |
4017 if(! mwConversation_isPending(conv)) |
| |
4018 mwConversation_open(conv); |
| |
4019 |
| |
4020 return 1; |
| |
4021 } |
| |
4022 } |
| |
4023 |
| |
4024 |
| |
4025 static unsigned int mw_prpl_send_typing(GaimConnection *gc, |
| |
4026 const char *name, |
| |
4027 GaimTypingState state) { |
| |
4028 |
| |
4029 struct mwGaimPluginData *pd; |
| |
4030 struct mwIdBlock who = { (char *) name, NULL }; |
| |
4031 struct mwConversation *conv; |
| |
4032 |
| |
4033 gpointer t = GINT_TO_POINTER(!! state); |
| |
4034 |
| |
4035 g_return_val_if_fail(gc != NULL, 0); |
| |
4036 pd = gc->proto_data; |
| |
4037 |
| |
4038 g_return_val_if_fail(pd != NULL, 0); |
| |
4039 |
| |
4040 conv = mwServiceIm_getConversation(pd->srvc_im, &who); |
| |
4041 |
| |
4042 if(mwConversation_isOpen(conv)) { |
| |
4043 mwConversation_send(conv, mwImSend_TYPING, t); |
| |
4044 |
| |
4045 } else if((state == GAIM_TYPING) || (state == GAIM_TYPED)) { |
| |
4046 /* only open a channel for sending typing notification, not for |
| |
4047 when typing has stopped. There's no point in re-opening a |
| |
4048 channel just to tell someone that this side isn't typing. */ |
| |
4049 |
| |
4050 convo_queue(conv, mwImSend_TYPING, t); |
| |
4051 |
| |
4052 if(! mwConversation_isPending(conv)) { |
| |
4053 mwConversation_open(conv); |
| |
4054 } |
| |
4055 } |
| |
4056 |
| |
4057 return 0; |
| |
4058 } |
| |
4059 |
| |
4060 |
| |
4061 static const char *mw_client_name(guint16 type) { |
| |
4062 switch(type) { |
| |
4063 case mwLogin_LIB: |
| |
4064 return "Lotus Binary Library"; |
| |
4065 |
| |
4066 case mwLogin_JAVA_WEB: |
| |
4067 return "Lotus Java Client Applet"; |
| |
4068 |
| |
4069 case mwLogin_BINARY: |
| |
4070 return "Lotus Sametime Connect"; |
| |
4071 |
| |
4072 case mwLogin_JAVA_APP: |
| |
4073 return "Lotus Java Client Application"; |
| |
4074 |
| |
4075 case mwLogin_LINKS: |
| |
4076 return "Lotus Sametime Links"; |
| |
4077 |
| |
4078 case mwLogin_NOTES_6_5: |
| |
4079 case mwLogin_NOTES_6_5_3: |
| |
4080 case mwLogin_NOTES_7_0_beta: |
| |
4081 case mwLogin_NOTES_7_0: |
| |
4082 return "Lotus Notes Client"; |
| |
4083 |
| |
4084 case mwLogin_ICT: |
| |
4085 case mwLogin_ICT_1_7_8_2: |
| |
4086 case mwLogin_ICT_SIP: |
| |
4087 return "IBM Community Tools"; |
| |
4088 |
| |
4089 case mwLogin_NOTESBUDDY_4_14: |
| |
4090 case mwLogin_NOTESBUDDY_4_15: |
| |
4091 case mwLogin_NOTESBUDDY_4_16: |
| |
4092 return "Alphaworks NotesBuddy"; |
| |
4093 |
| |
4094 case 0x1305: |
| |
4095 case 0x1306: |
| |
4096 case 0x1307: |
| |
4097 return "Lotus Sametime Connect 7.5"; |
| |
4098 |
| |
4099 case mwLogin_SANITY: |
| |
4100 return "Sanity"; |
| |
4101 |
| |
4102 case mwLogin_ST_PERL: |
| |
4103 return "ST-Send-Message"; |
| |
4104 |
| |
4105 case mwLogin_TRILLIAN: |
| |
4106 case mwLogin_TRILLIAN_IBM: |
| |
4107 return "Trillian"; |
| |
4108 |
| |
4109 case mwLogin_MEANWHILE: |
| |
4110 return "Meanwhile"; |
| |
4111 |
| |
4112 default: |
| |
4113 return NULL; |
| |
4114 } |
| |
4115 } |
| |
4116 |
| |
4117 |
| |
4118 static void mw_prpl_get_info(GaimConnection *gc, const char *who) { |
| |
4119 |
| |
4120 struct mwAwareIdBlock idb = { mwAware_USER, (char *) who, NULL }; |
| |
4121 |
| |
4122 struct mwGaimPluginData *pd; |
| |
4123 GaimAccount *acct; |
| |
4124 GaimBuddy *b; |
| |
4125 GaimNotifyUserInfo *user_info; |
| |
4126 char *tmp; |
| |
4127 const char *tmp2; |
| |
4128 |
| |
4129 g_return_if_fail(who != NULL); |
| |
4130 g_return_if_fail(*who != '\0'); |
| |
4131 |
| |
4132 pd = gc->proto_data; |
| |
4133 |
| |
4134 acct = gaim_connection_get_account(gc); |
| |
4135 b = gaim_find_buddy(acct, who); |
| |
4136 user_info = gaim_notify_user_info_new(); |
| |
4137 |
| |
4138 if(gaim_str_has_prefix(who, "@E ")) { |
| |
4139 gaim_notify_user_info_add_pair(user_info, _("External User"), NULL); |
| |
4140 } |
| |
4141 |
| |
4142 gaim_notify_user_info_add_pair(user_info, _("User ID"), who); |
| |
4143 |
| |
4144 if(b) { |
| |
4145 guint32 type; |
| |
4146 |
| |
4147 if(b->server_alias) { |
| |
4148 gaim_notify_user_info_add_pair(user_info, _("Full Name"), b->server_alias); |
| |
4149 } |
| |
4150 |
| |
4151 type = gaim_blist_node_get_int((GaimBlistNode *) b, BUDDY_KEY_CLIENT); |
| |
4152 if(type) { |
| |
4153 tmp = g_strdup(mw_client_name(type)); |
| |
4154 if (!tmp) |
| |
4155 tmp = g_strdup_printf(_("Unknown (0x%04x)<br>"), type); |
| |
4156 |
| |
4157 gaim_notify_user_info_add_pair(user_info, _("Last Known Client"), tmp); |
| |
4158 |
| |
4159 g_free(tmp); |
| |
4160 } |
| |
4161 } |
| |
4162 |
| |
4163 tmp = user_supports_text(pd->srvc_aware, who); |
| |
4164 if(tmp) { |
| |
4165 gaim_notify_user_info_add_pair(user_info, _("Supports"), tmp); |
| |
4166 g_free(tmp); |
| |
4167 } |
| |
4168 |
| |
4169 if(b) { |
| |
4170 gaim_notify_user_info_add_pair(user_info, _("Status"), status_text(b)); |
| |
4171 |
| |
4172 /* XXX Is this adding a status message in its own section rather than with the "Status" label? */ |
| |
4173 tmp2 = mwServiceAware_getText(pd->srvc_aware, &idb); |
| |
4174 if(tmp2) { |
| |
4175 tmp = g_markup_escape_text(tmp2, -1); |
| |
4176 gaim_notify_user_info_add_section_break(user_info); |
| |
4177 gaim_notify_user_info_add_pair(user_info, NULL, tmp); |
| |
4178 g_free(tmp); |
| |
4179 } |
| |
4180 } |
| |
4181 |
| |
4182 /* @todo emit a signal to allow a plugin to override the display of |
| |
4183 this notification, so that it can create its own */ |
| |
4184 |
| |
4185 gaim_notify_userinfo(gc, who, user_info, NULL, NULL); |
| |
4186 gaim_notify_user_info_destroy(user_info); |
| |
4187 } |
| |
4188 |
| |
4189 |
| |
4190 static void mw_prpl_set_status(GaimAccount *acct, GaimStatus *status) { |
| |
4191 GaimConnection *gc; |
| |
4192 const char *state; |
| |
4193 char *message = NULL; |
| |
4194 struct mwSession *session; |
| |
4195 struct mwUserStatus stat; |
| |
4196 |
| |
4197 g_return_if_fail(acct != NULL); |
| |
4198 gc = gaim_account_get_connection(acct); |
| |
4199 |
| |
4200 state = gaim_status_get_id(status); |
| |
4201 |
| |
4202 DEBUG_INFO("Set status to %s\n", gaim_status_get_name(status)); |
| |
4203 |
| |
4204 g_return_if_fail(gc != NULL); |
| |
4205 |
| |
4206 session = gc_to_session(gc); |
| |
4207 g_return_if_fail(session != NULL); |
| |
4208 |
| |
4209 /* get a working copy of the current status */ |
| |
4210 mwUserStatus_clone(&stat, mwSession_getUserStatus(session)); |
| |
4211 |
| |
4212 /* determine the state */ |
| |
4213 if(! strcmp(state, MW_STATE_ACTIVE)) { |
| |
4214 stat.status = mwStatus_ACTIVE; |
| |
4215 |
| |
4216 } else if(! strcmp(state, MW_STATE_AWAY)) { |
| |
4217 stat.status = mwStatus_AWAY; |
| |
4218 |
| |
4219 } else if(! strcmp(state, MW_STATE_BUSY)) { |
| |
4220 stat.status = mwStatus_BUSY; |
| |
4221 } |
| |
4222 |
| |
4223 /* determine the message */ |
| |
4224 message = (char *) gaim_status_get_attr_string(status, MW_STATE_MESSAGE); |
| |
4225 |
| |
4226 if(message) { |
| |
4227 /* all the possible non-NULL values of message up to this point |
| |
4228 are const, so we don't need to free them */ |
| |
4229 message = gaim_markup_strip_html(message); |
| |
4230 } |
| |
4231 |
| |
4232 /* out with the old */ |
| |
4233 g_free(stat.desc); |
| |
4234 |
| |
4235 /* in with the new */ |
| |
4236 stat.desc = (char *) message; |
| |
4237 |
| |
4238 mwSession_setUserStatus(session, &stat); |
| |
4239 mwUserStatus_clear(&stat); |
| |
4240 } |
| |
4241 |
| |
4242 |
| |
4243 static void mw_prpl_set_idle(GaimConnection *gc, int t) { |
| |
4244 struct mwSession *session; |
| |
4245 struct mwUserStatus stat; |
| |
4246 |
| |
4247 |
| |
4248 session = gc_to_session(gc); |
| |
4249 g_return_if_fail(session != NULL); |
| |
4250 |
| |
4251 mwUserStatus_clone(&stat, mwSession_getUserStatus(session)); |
| |
4252 |
| |
4253 if(t) { |
| |
4254 time_t now = time(NULL); |
| |
4255 stat.time = now - t; |
| |
4256 |
| |
4257 } else { |
| |
4258 stat.time = 0; |
| |
4259 } |
| |
4260 |
| |
4261 if(t > 0 && stat.status == mwStatus_ACTIVE) { |
| |
4262 /* we were active and went idle, so change the status to IDLE. */ |
| |
4263 stat.status = mwStatus_IDLE; |
| |
4264 |
| |
4265 } else if(t == 0 && stat.status == mwStatus_IDLE) { |
| |
4266 /* we only become idle automatically, so change back to ACTIVE */ |
| |
4267 stat.status = mwStatus_ACTIVE; |
| |
4268 } |
| |
4269 |
| |
4270 mwSession_setUserStatus(session, &stat); |
| |
4271 mwUserStatus_clear(&stat); |
| |
4272 } |
| |
4273 |
| |
4274 |
| |
4275 static void notify_im(GaimConnection *gc, GList *row, void *user_data) { |
| |
4276 GaimAccount *acct; |
| |
4277 GaimConversation *conv; |
| |
4278 char *id; |
| |
4279 |
| |
4280 acct = gaim_connection_get_account(gc); |
| |
4281 id = g_list_nth_data(row, 1); |
| |
4282 conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, id, acct); |
| |
4283 if(! conv) conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, acct, id); |
| |
4284 gaim_conversation_present(conv); |
| |
4285 } |
| |
4286 |
| |
4287 |
| |
4288 static void notify_add(GaimConnection *gc, GList *row, void *user_data) { |
| |
4289 BuddyAddData *data = user_data; |
| |
4290 char *group_name = NULL; |
| |
4291 |
| |
4292 if (data && data->group) { |
| |
4293 group_name = data->group->name; |
| |
4294 } |
| |
4295 |
| |
4296 gaim_blist_request_add_buddy(gaim_connection_get_account(gc), |
| |
4297 g_list_nth_data(row, 1), group_name, |
| |
4298 g_list_nth_data(row, 0)); |
| |
4299 } |
| |
4300 |
| |
4301 |
| |
4302 static void notify_close(gpointer data) { |
| |
4303 if (data) { |
| |
4304 g_free(data); |
| |
4305 } |
| |
4306 } |
| |
4307 |
| |
4308 |
| |
4309 static void multi_resolved_query(struct mwResolveResult *result, |
| |
4310 GaimConnection *gc, gpointer data) { |
| |
4311 GList *l; |
| |
4312 const char *msgA; |
| |
4313 const char *msgB; |
| |
4314 char *msg; |
| |
4315 |
| |
4316 GaimNotifySearchResults *sres; |
| |
4317 GaimNotifySearchColumn *scol; |
| |
4318 |
| |
4319 sres = gaim_notify_searchresults_new(); |
| |
4320 |
| |
4321 scol = gaim_notify_searchresults_column_new(_("User Name")); |
| |
4322 gaim_notify_searchresults_column_add(sres, scol); |
| |
4323 |
| |
4324 scol = gaim_notify_searchresults_column_new(_("Sametime ID")); |
| |
4325 gaim_notify_searchresults_column_add(sres, scol); |
| |
4326 |
| |
4327 gaim_notify_searchresults_button_add(sres, GAIM_NOTIFY_BUTTON_IM, |
| |
4328 notify_im); |
| |
4329 |
| |
4330 gaim_notify_searchresults_button_add(sres, GAIM_NOTIFY_BUTTON_ADD, |
| |
4331 notify_add); |
| |
4332 |
| |
4333 for(l = result->matches; l; l = l->next) { |
| |
4334 struct mwResolveMatch *match = l->data; |
| |
4335 GList *row = NULL; |
| |
4336 |
| |
4337 DEBUG_INFO("multi resolve: %s, %s\n", |
| |
4338 NSTR(match->id), NSTR(match->name)); |
| |
4339 |
| |
4340 if(!match->id || !match->name) |
| |
4341 continue; |
| |
4342 |
| |
4343 row = g_list_append(row, g_strdup(match->name)); |
| |
4344 row = g_list_append(row, g_strdup(match->id)); |
| |
4345 gaim_notify_searchresults_row_add(sres, row); |
| |
4346 } |
| |
4347 |
| |
4348 msgA = _("An ambiguous user ID was entered"); |
| |
4349 msgB = _("The identifier '%s' may possibly refer to any of the following" |
| |
4350 " users. Please select the correct user from the list below to" |
| |
4351 " add them to your buddy list."); |
| |
4352 msg = g_strdup_printf(msgB, result->name); |
| |
4353 |
| |
4354 gaim_notify_searchresults(gc, _("Select User"), |
| |
4355 msgA, msg, sres, notify_close, data); |
| |
4356 |
| |
4357 g_free(msg); |
| |
4358 } |
| |
4359 |
| |
4360 |
| |
4361 static void add_buddy_resolved(struct mwServiceResolve *srvc, |
| |
4362 guint32 id, guint32 code, GList *results, |
| |
4363 gpointer b) { |
| |
4364 |
| |
4365 struct mwResolveResult *res = NULL; |
| |
4366 BuddyAddData *data = b; |
| |
4367 GaimBuddy *buddy = NULL; |
| |
4368 GaimConnection *gc; |
| |
4369 struct mwGaimPluginData *pd; |
| |
4370 |
| |
4371 if (data) { |
| |
4372 buddy = data->buddy; |
| |
4373 } |
| |
4374 |
| |
4375 gc = gaim_account_get_connection(buddy->account); |
| |
4376 pd = gc->proto_data; |
| |
4377 |
| |
4378 if(results) |
| |
4379 res = results->data; |
| |
4380 |
| |
4381 if(!code && res && res->matches) { |
| |
4382 if(g_list_length(res->matches) == 1) { |
| |
4383 struct mwResolveMatch *match = res->matches->data; |
| |
4384 |
| |
4385 /* only one? that might be the right one! */ |
| |
4386 if(strcmp(res->name, match->id)) { |
| |
4387 /* uh oh, the single result isn't identical to the search |
| |
4388 term, better safe then sorry, so let's make sure it's who |
| |
4389 the user meant to add */ |
| |
4390 gaim_blist_remove_buddy(buddy); |
| |
4391 multi_resolved_query(res, gc, data); |
| |
4392 |
| |
4393 } else { |
| |
4394 |
| |
4395 /* same person, set the server alias */ |
| |
4396 gaim_blist_server_alias_buddy(buddy, match->name); |
| |
4397 gaim_blist_node_set_string((GaimBlistNode *) buddy, |
| |
4398 BUDDY_KEY_NAME, match->name); |
| |
4399 |
| |
4400 /* subscribe to awareness */ |
| |
4401 buddy_add(pd, buddy); |
| |
4402 |
| |
4403 blist_schedule(pd); |
| |
4404 |
| |
4405 g_free(data); |
| |
4406 } |
| |
4407 |
| |
4408 } else { |
| |
4409 /* prompt user if more than one match was returned */ |
| |
4410 gaim_blist_remove_buddy(buddy); |
| |
4411 multi_resolved_query(res, gc, data); |
| |
4412 } |
| |
4413 |
| |
4414 return; |
| |
4415 } |
| |
4416 |
| |
4417 #if 0 |
| |
4418 /* fall-through indicates that we couldn't find a matching user in |
| |
4419 the resolve service (ether error or zero results), so we remove |
| |
4420 this buddy */ |
| |
4421 |
| |
4422 /* note: I can't really think of a good reason to alter the buddy |
| |
4423 list in any way. There has been at least one report where the |
| |
4424 resolve service isn't returning correct results anyway, so let's |
| |
4425 just leave them in the list. I'm just going to if0 this section |
| |
4426 out unless I can think of a very good reason to do this. -siege */ |
| |
4427 |
| |
4428 DEBUG_INFO("no such buddy in community\n"); |
| |
4429 gaim_blist_remove_buddy(buddy); |
| |
4430 blist_schedule(pd); |
| |
4431 |
| |
4432 if(res && res->name) { |
| |
4433 /* compose and display an error message */ |
| |
4434 const char *msgA; |
| |
4435 const char *msgB; |
| |
4436 char *msg; |
| |
4437 |
| |
4438 msgA = _("Unable to add user: user not found"); |
| |
4439 |
| |
4440 msgB = _("The identifier '%s' did not match any users in your" |
| |
4441 " Sametime community. This entry has been removed from" |
| |
4442 " your buddy list."); |
| |
4443 msg = g_strdup_printf(msgB, NSTR(res->name)); |
| |
4444 |
| |
4445 gaim_notify_error(gc, _("Unable to add user"), msgA, msg); |
| |
4446 |
| |
4447 g_free(msg); |
| |
4448 } |
| |
4449 #endif |
| |
4450 } |
| |
4451 |
| |
4452 |
| |
4453 static void mw_prpl_add_buddy(GaimConnection *gc, |
| |
4454 GaimBuddy *buddy, |
| |
4455 GaimGroup *group) { |
| |
4456 |
| |
4457 struct mwGaimPluginData *pd; |
| |
4458 struct mwServiceResolve *srvc; |
| |
4459 GList *query; |
| |
4460 enum mwResolveFlag flags; |
| |
4461 guint32 req; |
| |
4462 |
| |
4463 BuddyAddData *data; |
| |
4464 |
| |
4465 data = g_new0(BuddyAddData, 1); |
| |
4466 data->buddy = buddy; |
| |
4467 data->group = group; |
| |
4468 |
| |
4469 pd = gc->proto_data; |
| |
4470 srvc = pd->srvc_resolve; |
| |
4471 |
| |
4472 /* catch external buddies. They won't be in the resolve service */ |
| |
4473 if(buddy_is_external(buddy)) { |
| |
4474 buddy_add(pd, buddy); |
| |
4475 return; |
| |
4476 } |
| |
4477 |
| |
4478 query = g_list_prepend(NULL, buddy->name); |
| |
4479 flags = mwResolveFlag_FIRST | mwResolveFlag_USERS; |
| |
4480 |
| |
4481 req = mwServiceResolve_resolve(srvc, query, flags, add_buddy_resolved, |
| |
4482 data, NULL); |
| |
4483 g_list_free(query); |
| |
4484 |
| |
4485 if(req == SEARCH_ERROR) { |
| |
4486 gaim_blist_remove_buddy(buddy); |
| |
4487 blist_schedule(pd); |
| |
4488 } |
| |
4489 } |
| |
4490 |
| |
4491 |
| |
4492 static void foreach_add_buddies(GaimGroup *group, GList *buddies, |
| |
4493 struct mwGaimPluginData *pd) { |
| |
4494 struct mwAwareList *list; |
| |
4495 |
| |
4496 list = list_ensure(pd, group); |
| |
4497 mwAwareList_addAware(list, buddies); |
| |
4498 g_list_free(buddies); |
| |
4499 } |
| |
4500 |
| |
4501 |
| |
4502 static void mw_prpl_add_buddies(GaimConnection *gc, |
| |
4503 GList *buddies, |
| |
4504 GList *groups) { |
| |
4505 |
| |
4506 struct mwGaimPluginData *pd; |
| |
4507 GHashTable *group_sets; |
| |
4508 struct mwAwareIdBlock *idbs, *idb; |
| |
4509 |
| |
4510 pd = gc->proto_data; |
| |
4511 |
| |
4512 /* map GaimGroup:GList of mwAwareIdBlock */ |
| |
4513 group_sets = g_hash_table_new(g_direct_hash, g_direct_equal); |
| |
4514 |
| |
4515 /* bunch of mwAwareIdBlock allocated at once, free'd at once */ |
| |
4516 idb = idbs = g_new(struct mwAwareIdBlock, g_list_length(buddies)); |
| |
4517 |
| |
4518 /* first pass collects mwAwareIdBlock lists for each group */ |
| |
4519 for(; buddies; buddies = buddies->next) { |
| |
4520 GaimBuddy *b = buddies->data; |
| |
4521 GaimGroup *g; |
| |
4522 const char *fn; |
| |
4523 GList *l; |
| |
4524 |
| |
4525 /* nab the saved server alias and stick it on the buddy */ |
| |
4526 fn = gaim_blist_node_get_string((GaimBlistNode *) b, BUDDY_KEY_NAME); |
| |
4527 gaim_blist_server_alias_buddy(b, fn); |
| |
4528 |
| |
4529 /* convert GaimBuddy into a mwAwareIdBlock */ |
| |
4530 idb->type = mwAware_USER; |
| |
4531 idb->user = (char *) b->name; |
| |
4532 idb->community = NULL; |
| |
4533 |
| |
4534 /* put idb into the list associated with the buddy's group */ |
| |
4535 g = gaim_buddy_get_group(b); |
| |
4536 l = g_hash_table_lookup(group_sets, g); |
| |
4537 l = g_list_prepend(l, idb++); |
| |
4538 g_hash_table_insert(group_sets, g, l); |
| |
4539 } |
| |
4540 |
| |
4541 /* each group's buddies get added in one shot, and schedule the blist |
| |
4542 for saving */ |
| |
4543 g_hash_table_foreach(group_sets, (GHFunc) foreach_add_buddies, pd); |
| |
4544 blist_schedule(pd); |
| |
4545 |
| |
4546 /* cleanup */ |
| |
4547 g_hash_table_destroy(group_sets); |
| |
4548 g_free(idbs); |
| |
4549 } |
| |
4550 |
| |
4551 |
| |
4552 static void mw_prpl_remove_buddy(GaimConnection *gc, |
| |
4553 GaimBuddy *buddy, GaimGroup *group) { |
| |
4554 |
| |
4555 struct mwGaimPluginData *pd; |
| |
4556 struct mwAwareIdBlock idb = { mwAware_USER, buddy->name, NULL }; |
| |
4557 struct mwAwareList *list; |
| |
4558 |
| |
4559 GList *rem = g_list_prepend(NULL, &idb); |
| |
4560 |
| |
4561 pd = gc->proto_data; |
| |
4562 group = gaim_buddy_get_group(buddy); |
| |
4563 list = list_ensure(pd, group); |
| |
4564 |
| |
4565 mwAwareList_removeAware(list, rem); |
| |
4566 blist_schedule(pd); |
| |
4567 |
| |
4568 g_list_free(rem); |
| |
4569 } |
| |
4570 |
| |
4571 |
| |
4572 static void privacy_fill(struct mwPrivacyInfo *priv, |
| |
4573 GSList *members) { |
| |
4574 |
| |
4575 struct mwUserItem *u; |
| |
4576 guint count; |
| |
4577 |
| |
4578 count = g_slist_length(members); |
| |
4579 DEBUG_INFO("privacy_fill: %u members\n", count); |
| |
4580 |
| |
4581 priv->count = count; |
| |
4582 priv->users = g_new0(struct mwUserItem, count); |
| |
4583 |
| |
4584 while(count--) { |
| |
4585 u = priv->users + count; |
| |
4586 u->id = members->data; |
| |
4587 members = members->next; |
| |
4588 } |
| |
4589 } |
| |
4590 |
| |
4591 |
| |
4592 static void mw_prpl_set_permit_deny(GaimConnection *gc) { |
| |
4593 GaimAccount *acct; |
| |
4594 struct mwGaimPluginData *pd; |
| |
4595 struct mwSession *session; |
| |
4596 |
| |
4597 struct mwPrivacyInfo privacy = { |
| |
4598 .deny = FALSE, |
| |
4599 .count = 0, |
| |
4600 .users = NULL, |
| |
4601 }; |
| |
4602 |
| |
4603 g_return_if_fail(gc != NULL); |
| |
4604 |
| |
4605 acct = gaim_connection_get_account(gc); |
| |
4606 g_return_if_fail(acct != NULL); |
| |
4607 |
| |
4608 pd = gc->proto_data; |
| |
4609 g_return_if_fail(pd != NULL); |
| |
4610 |
| |
4611 session = pd->session; |
| |
4612 g_return_if_fail(session != NULL); |
| |
4613 |
| |
4614 switch(acct->perm_deny) { |
| |
4615 case GAIM_PRIVACY_DENY_USERS: |
| |
4616 DEBUG_INFO("GAIM_PRIVACY_DENY_USERS\n"); |
| |
4617 privacy_fill(&privacy, acct->deny); |
| |
4618 privacy.deny = TRUE; |
| |
4619 break; |
| |
4620 |
| |
4621 case GAIM_PRIVACY_ALLOW_ALL: |
| |
4622 DEBUG_INFO("GAIM_PRIVACY_ALLOW_ALL\n"); |
| |
4623 privacy.deny = TRUE; |
| |
4624 break; |
| |
4625 |
| |
4626 case GAIM_PRIVACY_ALLOW_USERS: |
| |
4627 DEBUG_INFO("GAIM_PRIVACY_ALLOW_USERS\n"); |
| |
4628 privacy_fill(&privacy, acct->permit); |
| |
4629 privacy.deny = FALSE; |
| |
4630 break; |
| |
4631 |
| |
4632 case GAIM_PRIVACY_DENY_ALL: |
| |
4633 DEBUG_INFO("GAIM_PRIVACY_DENY_ALL\n"); |
| |
4634 privacy.deny = FALSE; |
| |
4635 break; |
| |
4636 |
| |
4637 default: |
| |
4638 DEBUG_INFO("acct->perm_deny is 0x%x\n", acct->perm_deny); |
| |
4639 return; |
| |
4640 } |
| |
4641 |
| |
4642 mwSession_setPrivacyInfo(session, &privacy); |
| |
4643 g_free(privacy.users); |
| |
4644 } |
| |
4645 |
| |
4646 |
| |
4647 static void mw_prpl_add_permit(GaimConnection *gc, const char *name) { |
| |
4648 mw_prpl_set_permit_deny(gc); |
| |
4649 } |
| |
4650 |
| |
4651 |
| |
4652 static void mw_prpl_add_deny(GaimConnection *gc, const char *name) { |
| |
4653 mw_prpl_set_permit_deny(gc); |
| |
4654 } |
| |
4655 |
| |
4656 |
| |
4657 static void mw_prpl_rem_permit(GaimConnection *gc, const char *name) { |
| |
4658 mw_prpl_set_permit_deny(gc); |
| |
4659 } |
| |
4660 |
| |
4661 |
| |
4662 static void mw_prpl_rem_deny(GaimConnection *gc, const char *name) { |
| |
4663 mw_prpl_set_permit_deny(gc); |
| |
4664 } |
| |
4665 |
| |
4666 |
| |
4667 static struct mwConference *conf_find(struct mwServiceConference *srvc, |
| |
4668 const char *name) { |
| |
4669 GList *l, *ll; |
| |
4670 struct mwConference *conf = NULL; |
| |
4671 |
| |
4672 ll = mwServiceConference_getConferences(srvc); |
| |
4673 for(l = ll; l; l = l->next) { |
| |
4674 struct mwConference *c = l->data; |
| |
4675 if(! strcmp(name, mwConference_getName(c))) { |
| |
4676 conf = c; |
| |
4677 break; |
| |
4678 } |
| |
4679 } |
| |
4680 g_list_free(ll); |
| |
4681 |
| |
4682 return conf; |
| |
4683 } |
| |
4684 |
| |
4685 |
| |
4686 static void mw_prpl_join_chat(GaimConnection *gc, |
| |
4687 GHashTable *components) { |
| |
4688 |
| |
4689 struct mwGaimPluginData *pd; |
| |
4690 char *c, *t; |
| |
4691 |
| |
4692 pd = gc->proto_data; |
| |
4693 |
| |
4694 c = g_hash_table_lookup(components, CHAT_KEY_NAME); |
| |
4695 t = g_hash_table_lookup(components, CHAT_KEY_TOPIC); |
| |
4696 |
| |
4697 if(g_hash_table_lookup(components, CHAT_KEY_IS_PLACE)) { |
| |
4698 /* use place service */ |
| |
4699 struct mwServicePlace *srvc; |
| |
4700 struct mwPlace *place = NULL; |
| |
4701 |
| |
4702 srvc = pd->srvc_place; |
| |
4703 place = mwPlace_new(srvc, c, t); |
| |
4704 mwPlace_open(place); |
| |
4705 |
| |
4706 } else { |
| |
4707 /* use conference service */ |
| |
4708 struct mwServiceConference *srvc; |
| |
4709 struct mwConference *conf = NULL; |
| |
4710 |
| |
4711 srvc = pd->srvc_conf; |
| |
4712 if(c) conf = conf_find(srvc, c); |
| |
4713 |
| |
4714 if(conf) { |
| |
4715 DEBUG_INFO("accepting conference invitation\n"); |
| |
4716 mwConference_accept(conf); |
| |
4717 |
| |
4718 } else { |
| |
4719 DEBUG_INFO("creating new conference\n"); |
| |
4720 conf = mwConference_new(srvc, t); |
| |
4721 mwConference_open(conf); |
| |
4722 } |
| |
4723 } |
| |
4724 } |
| |
4725 |
| |
4726 |
| |
4727 static void mw_prpl_reject_chat(GaimConnection *gc, |
| |
4728 GHashTable *components) { |
| |
4729 |
| |
4730 struct mwGaimPluginData *pd; |
| |
4731 struct mwServiceConference *srvc; |
| |
4732 char *c; |
| |
4733 |
| |
4734 pd = gc->proto_data; |
| |
4735 srvc = pd->srvc_conf; |
| |
4736 |
| |
4737 if(g_hash_table_lookup(components, CHAT_KEY_IS_PLACE)) { |
| |
4738 ; /* nothing needs doing */ |
| |
4739 |
| |
4740 } else { |
| |
4741 /* reject conference */ |
| |
4742 c = g_hash_table_lookup(components, CHAT_KEY_NAME); |
| |
4743 if(c) { |
| |
4744 struct mwConference *conf = conf_find(srvc, c); |
| |
4745 if(conf) mwConference_reject(conf, ERR_SUCCESS, "Declined"); |
| |
4746 } |
| |
4747 } |
| |
4748 } |
| |
4749 |
| |
4750 |
| |
4751 static char *mw_prpl_get_chat_name(GHashTable *components) { |
| |
4752 return g_hash_table_lookup(components, CHAT_KEY_NAME); |
| |
4753 } |
| |
4754 |
| |
4755 |
| |
4756 static void mw_prpl_chat_invite(GaimConnection *gc, |
| |
4757 int id, |
| |
4758 const char *invitation, |
| |
4759 const char *who) { |
| |
4760 |
| |
4761 struct mwGaimPluginData *pd; |
| |
4762 struct mwConference *conf; |
| |
4763 struct mwPlace *place; |
| |
4764 struct mwIdBlock idb = { (char *) who, NULL }; |
| |
4765 |
| |
4766 pd = gc->proto_data; |
| |
4767 g_return_if_fail(pd != NULL); |
| |
4768 |
| |
4769 conf = ID_TO_CONF(pd, id); |
| |
4770 |
| |
4771 if(conf) { |
| |
4772 mwConference_invite(conf, &idb, invitation); |
| |
4773 return; |
| |
4774 } |
| |
4775 |
| |
4776 place = ID_TO_PLACE(pd, id); |
| |
4777 g_return_if_fail(place != NULL); |
| |
4778 |
| |
4779 /* @todo: use the IM service for invitation */ |
| |
4780 mwPlace_legacyInvite(place, &idb, invitation); |
| |
4781 } |
| |
4782 |
| |
4783 |
| |
4784 static void mw_prpl_chat_leave(GaimConnection *gc, |
| |
4785 int id) { |
| |
4786 |
| |
4787 struct mwGaimPluginData *pd; |
| |
4788 struct mwConference *conf; |
| |
4789 |
| |
4790 pd = gc->proto_data; |
| |
4791 |
| |
4792 g_return_if_fail(pd != NULL); |
| |
4793 conf = ID_TO_CONF(pd, id); |
| |
4794 |
| |
4795 if(conf) { |
| |
4796 mwConference_destroy(conf, ERR_SUCCESS, "Leaving"); |
| |
4797 |
| |
4798 } else { |
| |
4799 struct mwPlace *place = ID_TO_PLACE(pd, id); |
| |
4800 g_return_if_fail(place != NULL); |
| |
4801 |
| |
4802 mwPlace_destroy(place, ERR_SUCCESS); |
| |
4803 } |
| |
4804 } |
| |
4805 |
| |
4806 |
| |
4807 static void mw_prpl_chat_whisper(GaimConnection *gc, |
| |
4808 int id, |
| |
4809 const char *who, |
| |
4810 const char *message) { |
| |
4811 |
| |
4812 mw_prpl_send_im(gc, who, message, 0); |
| |
4813 } |
| |
4814 |
| |
4815 |
| |
4816 static int mw_prpl_chat_send(GaimConnection *gc, |
| |
4817 int id, |
| |
4818 const char *message, |
| |
4819 GaimMessageFlags flags) { |
| |
4820 |
| |
4821 struct mwGaimPluginData *pd; |
| |
4822 struct mwConference *conf; |
| |
4823 char *msg; |
| |
4824 int ret; |
| |
4825 |
| |
4826 pd = gc->proto_data; |
| |
4827 |
| |
4828 g_return_val_if_fail(pd != NULL, 0); |
| |
4829 conf = ID_TO_CONF(pd, id); |
| |
4830 |
| |
4831 msg = gaim_markup_strip_html(message); |
| |
4832 |
| |
4833 if(conf) { |
| |
4834 ret = ! mwConference_sendText(conf, message); |
| |
4835 |
| |
4836 } else { |
| |
4837 struct mwPlace *place = ID_TO_PLACE(pd, id); |
| |
4838 g_return_val_if_fail(place != NULL, 0); |
| |
4839 |
| |
4840 ret = ! mwPlace_sendText(place, message); |
| |
4841 } |
| |
4842 |
| |
4843 g_free(msg); |
| |
4844 return ret; |
| |
4845 } |
| |
4846 |
| |
4847 |
| |
4848 static void mw_prpl_keepalive(GaimConnection *gc) { |
| |
4849 struct mwSession *session; |
| |
4850 |
| |
4851 g_return_if_fail(gc != NULL); |
| |
4852 |
| |
4853 session = gc_to_session(gc); |
| |
4854 g_return_if_fail(session != NULL); |
| |
4855 |
| |
4856 mwSession_sendKeepalive(session); |
| |
4857 } |
| |
4858 |
| |
4859 |
| |
4860 static void mw_prpl_alias_buddy(GaimConnection *gc, |
| |
4861 const char *who, |
| |
4862 const char *alias) { |
| |
4863 |
| |
4864 struct mwGaimPluginData *pd = gc->proto_data; |
| |
4865 g_return_if_fail(pd != NULL); |
| |
4866 |
| |
4867 /* it's a change to the buddy list, so we've gotta reflect that in |
| |
4868 the server copy */ |
| |
4869 |
| |
4870 blist_schedule(pd); |
| |
4871 } |
| |
4872 |
| |
4873 |
| |
4874 static void mw_prpl_group_buddy(GaimConnection *gc, |
| |
4875 const char *who, |
| |
4876 const char *old_group, |
| |
4877 const char *new_group) { |
| |
4878 |
| |
4879 struct mwAwareIdBlock idb = { mwAware_USER, (char *) who, NULL }; |
| |
4880 GList *gl = g_list_prepend(NULL, &idb); |
| |
4881 |
| |
4882 struct mwGaimPluginData *pd = gc->proto_data; |
| |
4883 GaimGroup *group; |
| |
4884 struct mwAwareList *list; |
| |
4885 |
| |
4886 /* add who to new_group's aware list */ |
| |
4887 group = gaim_find_group(new_group); |
| |
4888 list = list_ensure(pd, group); |
| |
4889 mwAwareList_addAware(list, gl); |
| |
4890 |
| |
4891 /* remove who from old_group's aware list */ |
| |
4892 group = gaim_find_group(old_group); |
| |
4893 list = list_ensure(pd, group); |
| |
4894 mwAwareList_removeAware(list, gl); |
| |
4895 |
| |
4896 g_list_free(gl); |
| |
4897 |
| |
4898 /* schedule the changes to be saved */ |
| |
4899 blist_schedule(pd); |
| |
4900 } |
| |
4901 |
| |
4902 |
| |
4903 static void mw_prpl_rename_group(GaimConnection *gc, |
| |
4904 const char *old, |
| |
4905 GaimGroup *group, |
| |
4906 GList *buddies) { |
| |
4907 |
| |
4908 struct mwGaimPluginData *pd = gc->proto_data; |
| |
4909 g_return_if_fail(pd != NULL); |
| |
4910 |
| |
4911 /* it's a change in the buddy list, so we've gotta reflect that in |
| |
4912 the server copy. Also, having this function should prevent all |
| |
4913 those buddies from being removed and re-added. We don't really |
| |
4914 give a crap what the group is named in Gaim other than to record |
| |
4915 that as the group name/alias */ |
| |
4916 |
| |
4917 blist_schedule(pd); |
| |
4918 } |
| |
4919 |
| |
4920 |
| |
4921 static void mw_prpl_buddy_free(GaimBuddy *buddy) { |
| |
4922 /* I don't think we have any cleanup for buddies yet */ |
| |
4923 ; |
| |
4924 } |
| |
4925 |
| |
4926 |
| |
4927 static void mw_prpl_convo_closed(GaimConnection *gc, const char *who) { |
| |
4928 struct mwGaimPluginData *pd = gc->proto_data; |
| |
4929 struct mwServiceIm *srvc; |
| |
4930 struct mwConversation *conv; |
| |
4931 struct mwIdBlock idb = { (char *) who, NULL }; |
| |
4932 |
| |
4933 g_return_if_fail(pd != NULL); |
| |
4934 |
| |
4935 srvc = pd->srvc_im; |
| |
4936 g_return_if_fail(srvc != NULL); |
| |
4937 |
| |
4938 conv = mwServiceIm_findConversation(srvc, &idb); |
| |
4939 if(! conv) return; |
| |
4940 |
| |
4941 if(mwConversation_isOpen(conv)) |
| |
4942 mwConversation_free(conv); |
| |
4943 } |
| |
4944 |
| |
4945 |
| |
4946 static const char *mw_prpl_normalize(const GaimAccount *account, |
| |
4947 const char *id) { |
| |
4948 |
| |
4949 /* code elsewhere assumes that the return value points to different |
| |
4950 memory than the passed value, but it won't free the normalized |
| |
4951 data. wtf? */ |
| |
4952 |
| |
4953 static char buf[BUF_LEN]; |
| |
4954 strncpy(buf, id, sizeof(buf)); |
| |
4955 return buf; |
| |
4956 } |
| |
4957 |
| |
4958 |
| |
4959 static void mw_prpl_remove_group(GaimConnection *gc, GaimGroup *group) { |
| |
4960 struct mwGaimPluginData *pd; |
| |
4961 struct mwAwareList *list; |
| |
4962 |
| |
4963 pd = gc->proto_data; |
| |
4964 g_return_if_fail(pd != NULL); |
| |
4965 g_return_if_fail(pd->group_list_map != NULL); |
| |
4966 |
| |
4967 list = g_hash_table_lookup(pd->group_list_map, group); |
| |
4968 |
| |
4969 if(list) { |
| |
4970 g_hash_table_remove(pd->group_list_map, list); |
| |
4971 g_hash_table_remove(pd->group_list_map, group); |
| |
4972 mwAwareList_free(list); |
| |
4973 |
| |
4974 blist_schedule(pd); |
| |
4975 } |
| |
4976 } |
| |
4977 |
| |
4978 |
| |
4979 static gboolean mw_prpl_can_receive_file(GaimConnection *gc, |
| |
4980 const char *who) { |
| |
4981 struct mwGaimPluginData *pd; |
| |
4982 struct mwServiceAware *srvc; |
| |
4983 GaimAccount *acct; |
| |
4984 |
| |
4985 g_return_val_if_fail(gc != NULL, FALSE); |
| |
4986 |
| |
4987 pd = gc->proto_data; |
| |
4988 g_return_val_if_fail(pd != NULL, FALSE); |
| |
4989 |
| |
4990 srvc = pd->srvc_aware; |
| |
4991 g_return_val_if_fail(srvc != NULL, FALSE); |
| |
4992 |
| |
4993 acct = gaim_connection_get_account(gc); |
| |
4994 g_return_val_if_fail(acct != NULL, FALSE); |
| |
4995 |
| |
4996 return gaim_find_buddy(acct, who) && |
| |
4997 user_supports(srvc, who, mwAttribute_FILE_TRANSFER); |
| |
4998 } |
| |
4999 |
| |
5000 |
| |
5001 static void ft_outgoing_init(GaimXfer *xfer) { |
| |
5002 GaimAccount *acct; |
| |
5003 GaimConnection *gc; |
| |
5004 |
| |
5005 struct mwGaimPluginData *pd; |
| |
5006 struct mwServiceFileTransfer *srvc; |
| |
5007 struct mwFileTransfer *ft; |
| |
5008 |
| |
5009 const char *filename; |
| |
5010 gsize filesize; |
| |
5011 FILE *fp; |
| |
5012 |
| |
5013 struct mwIdBlock idb = { NULL, NULL }; |
| |
5014 |
| |
5015 DEBUG_INFO("ft_outgoing_init\n"); |
| |
5016 |
| |
5017 acct = gaim_xfer_get_account(xfer); |
| |
5018 gc = gaim_account_get_connection(acct); |
| |
5019 pd = gc->proto_data; |
| |
5020 srvc = pd->srvc_ft; |
| |
5021 |
| |
5022 filename = gaim_xfer_get_local_filename(xfer); |
| |
5023 filesize = gaim_xfer_get_size(xfer); |
| |
5024 idb.user = xfer->who; |
| |
5025 |
| |
5026 gaim_xfer_update_progress(xfer); |
| |
5027 |
| |
5028 /* test that we can actually send the file */ |
| |
5029 fp = g_fopen(filename, "rb"); |
| |
5030 if(! fp) { |
| |
5031 char *msg = g_strdup_printf(_("Error reading file %s: \n%s\n"), |
| |
5032 filename, strerror(errno)); |
| |
5033 gaim_xfer_error(gaim_xfer_get_type(xfer), acct, xfer->who, msg); |
| |
5034 g_free(msg); |
| |
5035 return; |
| |
5036 } |
| |
5037 fclose(fp); |
| |
5038 |
| |
5039 { |
| |
5040 char *tmp = strrchr(filename, G_DIR_SEPARATOR); |
| |
5041 if(tmp++) filename = tmp; |
| |
5042 } |
| |
5043 |
| |
5044 ft = mwFileTransfer_new(srvc, &idb, NULL, filename, filesize); |
| |
5045 |
| |
5046 gaim_xfer_ref(xfer); |
| |
5047 mwFileTransfer_setClientData(ft, xfer, (GDestroyNotify) gaim_xfer_unref); |
| |
5048 xfer->data = ft; |
| |
5049 |
| |
5050 mwFileTransfer_offer(ft); |
| |
5051 } |
| |
5052 |
| |
5053 |
| |
5054 static void ft_outgoing_cancel(GaimXfer *xfer) { |
| |
5055 struct mwFileTransfer *ft = xfer->data; |
| |
5056 |
| |
5057 DEBUG_INFO("ft_outgoing_cancel called\n"); |
| |
5058 |
| |
5059 if(ft) mwFileTransfer_cancel(ft); |
| |
5060 } |
| |
5061 |
| |
5062 |
| |
5063 static GaimXfer *mw_prpl_new_xfer(GaimConnection *gc, const char *who) { |
| |
5064 GaimAccount *acct; |
| |
5065 GaimXfer *xfer; |
| |
5066 |
| |
5067 acct = gaim_connection_get_account(gc); |
| |
5068 |
| |
5069 xfer = gaim_xfer_new(acct, GAIM_XFER_SEND, who); |
| |
5070 if (xfer) |
| |
5071 { |
| |
5072 gaim_xfer_set_init_fnc(xfer, ft_outgoing_init); |
| |
5073 gaim_xfer_set_cancel_send_fnc(xfer, ft_outgoing_cancel); |
| |
5074 } |
| |
5075 |
| |
5076 return xfer; |
| |
5077 } |
| |
5078 |
| |
5079 static void mw_prpl_send_file(GaimConnection *gc, |
| |
5080 const char *who, const char *file) { |
| |
5081 |
| |
5082 GaimXfer *xfer = mw_prpl_new_xfer(gc, who); |
| |
5083 |
| |
5084 if(file) { |
| |
5085 DEBUG_INFO("file != NULL\n"); |
| |
5086 gaim_xfer_request_accepted(xfer, file); |
| |
5087 |
| |
5088 } else { |
| |
5089 DEBUG_INFO("file == NULL\n"); |
| |
5090 gaim_xfer_request(xfer); |
| |
5091 } |
| |
5092 } |
| |
5093 |
| |
5094 |
| |
5095 static GaimPluginProtocolInfo mw_prpl_info = { |
| |
5096 .options = OPT_PROTO_IM_IMAGE, |
| |
5097 .user_splits = NULL, /*< set in mw_plugin_init */ |
| |
5098 .protocol_options = NULL, /*< set in mw_plugin_init */ |
| |
5099 .icon_spec = NO_BUDDY_ICONS, |
| |
5100 .list_icon = mw_prpl_list_icon, |
| |
5101 .list_emblems = mw_prpl_list_emblems, |
| |
5102 .status_text = mw_prpl_status_text, |
| |
5103 .tooltip_text = mw_prpl_tooltip_text, |
| |
5104 .status_types = mw_prpl_status_types, |
| |
5105 .blist_node_menu = mw_prpl_blist_node_menu, |
| |
5106 .chat_info = mw_prpl_chat_info, |
| |
5107 .chat_info_defaults = mw_prpl_chat_info_defaults, |
| |
5108 .login = mw_prpl_login, |
| |
5109 .close = mw_prpl_close, |
| |
5110 .send_im = mw_prpl_send_im, |
| |
5111 .set_info = NULL, |
| |
5112 .send_typing = mw_prpl_send_typing, |
| |
5113 .get_info = mw_prpl_get_info, |
| |
5114 .set_status = mw_prpl_set_status, |
| |
5115 .set_idle = mw_prpl_set_idle, |
| |
5116 .change_passwd = NULL, |
| |
5117 .add_buddy = mw_prpl_add_buddy, |
| |
5118 .add_buddies = mw_prpl_add_buddies, |
| |
5119 .remove_buddy = mw_prpl_remove_buddy, |
| |
5120 .remove_buddies = NULL, |
| |
5121 .add_permit = mw_prpl_add_permit, |
| |
5122 .add_deny = mw_prpl_add_deny, |
| |
5123 .rem_permit = mw_prpl_rem_permit, |
| |
5124 .rem_deny = mw_prpl_rem_deny, |
| |
5125 .set_permit_deny = mw_prpl_set_permit_deny, |
| |
5126 .join_chat = mw_prpl_join_chat, |
| |
5127 .reject_chat = mw_prpl_reject_chat, |
| |
5128 .get_chat_name = mw_prpl_get_chat_name, |
| |
5129 .chat_invite = mw_prpl_chat_invite, |
| |
5130 .chat_leave = mw_prpl_chat_leave, |
| |
5131 .chat_whisper = mw_prpl_chat_whisper, |
| |
5132 .chat_send = mw_prpl_chat_send, |
| |
5133 .keepalive = mw_prpl_keepalive, |
| |
5134 .register_user = NULL, |
| |
5135 .get_cb_info = NULL, |
| |
5136 .get_cb_away = NULL, |
| |
5137 .alias_buddy = mw_prpl_alias_buddy, |
| |
5138 .group_buddy = mw_prpl_group_buddy, |
| |
5139 .rename_group = mw_prpl_rename_group, |
| |
5140 .buddy_free = mw_prpl_buddy_free, |
| |
5141 .convo_closed = mw_prpl_convo_closed, |
| |
5142 .normalize = mw_prpl_normalize, |
| |
5143 .set_buddy_icon = NULL, |
| |
5144 .remove_group = mw_prpl_remove_group, |
| |
5145 .get_cb_real_name = NULL, |
| |
5146 .set_chat_topic = NULL, |
| |
5147 .find_blist_chat = NULL, |
| |
5148 .roomlist_get_list = NULL, |
| |
5149 .roomlist_expand_category = NULL, |
| |
5150 .can_receive_file = mw_prpl_can_receive_file, |
| |
5151 .send_file = mw_prpl_send_file, |
| |
5152 .new_xfer = mw_prpl_new_xfer, |
| |
5153 .offline_message = NULL, |
| |
5154 .whiteboard_prpl_ops = NULL, |
| |
5155 .send_raw = NULL |
| |
5156 }; |
| |
5157 |
| |
5158 |
| |
5159 static GaimPluginPrefFrame * |
| |
5160 mw_plugin_get_plugin_pref_frame(GaimPlugin *plugin) { |
| |
5161 GaimPluginPrefFrame *frame; |
| |
5162 GaimPluginPref *pref; |
| |
5163 |
| |
5164 frame = gaim_plugin_pref_frame_new(); |
| |
5165 |
| |
5166 pref = gaim_plugin_pref_new_with_label(_("Remotely Stored Buddy List")); |
| |
5167 gaim_plugin_pref_frame_add(frame, pref); |
| |
5168 |
| |
5169 |
| |
5170 pref = gaim_plugin_pref_new_with_name(MW_PRPL_OPT_BLIST_ACTION); |
| |
5171 gaim_plugin_pref_set_label(pref, _("Buddy List Storage Mode")); |
| |
5172 |
| |
5173 gaim_plugin_pref_set_type(pref, GAIM_PLUGIN_PREF_CHOICE); |
| |
5174 gaim_plugin_pref_add_choice(pref, _("Local Buddy List Only"), |
| |
5175 GINT_TO_POINTER(blist_choice_LOCAL)); |
| |
5176 gaim_plugin_pref_add_choice(pref, _("Merge List from Server"), |
| |
5177 GINT_TO_POINTER(blist_choice_MERGE)); |
| |
5178 gaim_plugin_pref_add_choice(pref, _("Merge and Save List to Server"), |
| |
5179 GINT_TO_POINTER(blist_choice_STORE)); |
| |
5180 gaim_plugin_pref_add_choice(pref, _("Synchronize List with Server"), |
| |
5181 GINT_TO_POINTER(blist_choice_SYNCH)); |
| |
5182 |
| |
5183 gaim_plugin_pref_frame_add(frame, pref); |
| |
5184 |
| |
5185 return frame; |
| |
5186 } |
| |
5187 |
| |
5188 |
| |
5189 static GaimPluginUiInfo mw_plugin_ui_info = { |
| |
5190 .get_plugin_pref_frame = mw_plugin_get_plugin_pref_frame, |
| |
5191 }; |
| |
5192 |
| |
5193 |
| |
5194 static void st_import_action_cb(GaimConnection *gc, char *filename) { |
| |
5195 struct mwSametimeList *l; |
| |
5196 |
| |
5197 FILE *file; |
| |
5198 char buf[BUF_LEN]; |
| |
5199 size_t len; |
| |
5200 |
| |
5201 GString *str; |
| |
5202 |
| |
5203 file = g_fopen(filename, "r"); |
| |
5204 g_return_if_fail(file != NULL); |
| |
5205 |
| |
5206 str = g_string_new(NULL); |
| |
5207 while( (len = fread(buf, 1, BUF_LEN, file)) ) { |
| |
5208 g_string_append_len(str, buf, len); |
| |
5209 } |
| |
5210 |
| |
5211 fclose(file); |
| |
5212 |
| |
5213 l = mwSametimeList_load(str->str); |
| |
5214 g_string_free(str, TRUE); |
| |
5215 |
| |
5216 blist_merge(gc, l); |
| |
5217 mwSametimeList_free(l); |
| |
5218 } |
| |
5219 |
| |
5220 |
| |
5221 /** prompts for a file to import blist from */ |
| |
5222 static void st_import_action(GaimPluginAction *act) { |
| |
5223 GaimConnection *gc; |
| |
5224 GaimAccount *account; |
| |
5225 char *title; |
| |
5226 |
| |
5227 gc = act->context; |
| |
5228 account = gaim_connection_get_account(gc); |
| |
5229 title = g_strdup_printf(_("Import Sametime List for Account %s"), |
| |
5230 gaim_account_get_username(account)); |
| |
5231 |
| |
5232 gaim_request_file(gc, title, NULL, FALSE, |
| |
5233 G_CALLBACK(st_import_action_cb), NULL, |
| |
5234 gc); |
| |
5235 |
| |
5236 g_free(title); |
| |
5237 } |
| |
5238 |
| |
5239 |
| |
5240 static void st_export_action_cb(GaimConnection *gc, char *filename) { |
| |
5241 struct mwSametimeList *l; |
| |
5242 char *str; |
| |
5243 FILE *file; |
| |
5244 |
| |
5245 file = g_fopen(filename, "w"); |
| |
5246 g_return_if_fail(file != NULL); |
| |
5247 |
| |
5248 l = mwSametimeList_new(); |
| |
5249 blist_export(gc, l); |
| |
5250 str = mwSametimeList_store(l); |
| |
5251 mwSametimeList_free(l); |
| |
5252 |
| |
5253 fprintf(file, "%s", str); |
| |
5254 fclose(file); |
| |
5255 |
| |
5256 g_free(str); |
| |
5257 } |
| |
5258 |
| |
5259 |
| |
5260 /** prompts for a file to export blist to */ |
| |
5261 static void st_export_action(GaimPluginAction *act) { |
| |
5262 GaimConnection *gc; |
| |
5263 GaimAccount *account; |
| |
5264 char *title; |
| |
5265 |
| |
5266 gc = act->context; |
| |
5267 account = gaim_connection_get_account(gc); |
| |
5268 title = g_strdup_printf(_("Export Sametime List for Account %s"), |
| |
5269 gaim_account_get_username(account)); |
| |
5270 |
| |
5271 gaim_request_file(gc, title, NULL, TRUE, |
| |
5272 G_CALLBACK(st_export_action_cb), NULL, |
| |
5273 gc); |
| |
5274 |
| |
5275 g_free(title); |
| |
5276 } |
| |
5277 |
| |
5278 |
| |
5279 static void remote_group_multi_cleanup(gpointer ignore, |
| |
5280 GaimRequestFields *fields) { |
| |
5281 |
| |
5282 GaimRequestField *f; |
| |
5283 const GList *l; |
| |
5284 |
| |
5285 f = gaim_request_fields_get_field(fields, "group"); |
| |
5286 l = gaim_request_field_list_get_items(f); |
| |
5287 |
| |
5288 for(; l; l = l->next) { |
| |
5289 const char *i = l->data; |
| |
5290 struct named_id *res; |
| |
5291 |
| |
5292 res = gaim_request_field_list_get_data(f, i); |
| |
5293 |
| |
5294 g_free(res->id); |
| |
5295 g_free(res->name); |
| |
5296 g_free(res); |
| |
5297 } |
| |
5298 } |
| |
5299 |
| |
5300 |
| |
5301 static void remote_group_done(struct mwGaimPluginData *pd, |
| |
5302 const char *id, const char *name) { |
| |
5303 GaimConnection *gc; |
| |
5304 GaimAccount *acct; |
| |
5305 GaimGroup *group; |
| |
5306 GaimBlistNode *gn; |
| |
5307 const char *owner; |
| |
5308 |
| |
5309 g_return_if_fail(pd != NULL); |
| |
5310 |
| |
5311 gc = pd->gc; |
| |
5312 acct = gaim_connection_get_account(gc); |
| |
5313 |
| |
5314 /* collision checking */ |
| |
5315 group = gaim_find_group(name); |
| |
5316 if(group) { |
| |
5317 const char *msgA; |
| |
5318 const char *msgB; |
| |
5319 char *msg; |
| |
5320 |
| |
5321 msgA = _("Unable to add group: group exists"); |
| |
5322 msgB = _("A group named '%s' already exists in your buddy list."); |
| |
5323 msg = g_strdup_printf(msgB, name); |
| |
5324 |
| |
5325 gaim_notify_error(gc, _("Unable to add group"), msgA, msg); |
| |
5326 |
| |
5327 g_free(msg); |
| |
5328 return; |
| |
5329 } |
| |
5330 |
| |
5331 group = gaim_group_new(name); |
| |
5332 gn = (GaimBlistNode *) group; |
| |
5333 |
| |
5334 owner = gaim_account_get_username(acct); |
| |
5335 |
| |
5336 gaim_blist_node_set_string(gn, GROUP_KEY_NAME, id); |
| |
5337 gaim_blist_node_set_int(gn, GROUP_KEY_TYPE, mwSametimeGroup_DYNAMIC); |
| |
5338 gaim_blist_node_set_string(gn, GROUP_KEY_OWNER, owner); |
| |
5339 gaim_blist_add_group(group, NULL); |
| |
5340 |
| |
5341 group_add(pd, group); |
| |
5342 blist_schedule(pd); |
| |
5343 } |
| |
5344 |
| |
5345 |
| |
5346 static void remote_group_multi_cb(struct mwGaimPluginData *pd, |
| |
5347 GaimRequestFields *fields) { |
| |
5348 GaimRequestField *f; |
| |
5349 const GList *l; |
| |
5350 |
| |
5351 f = gaim_request_fields_get_field(fields, "group"); |
| |
5352 l = gaim_request_field_list_get_selected(f); |
| |
5353 |
| |
5354 if(l) { |
| |
5355 const char *i = l->data; |
| |
5356 struct named_id *res; |
| |
5357 |
| |
5358 res = gaim_request_field_list_get_data(f, i); |
| |
5359 remote_group_done(pd, res->id, res->name); |
| |
5360 } |
| |
5361 |
| |
5362 remote_group_multi_cleanup(NULL, fields); |
| |
5363 } |
| |
5364 |
| |
5365 |
| |
5366 static void remote_group_multi(struct mwResolveResult *result, |
| |
5367 struct mwGaimPluginData *pd) { |
| |
5368 |
| |
5369 GaimRequestFields *fields; |
| |
5370 GaimRequestFieldGroup *g; |
| |
5371 GaimRequestField *f; |
| |
5372 GList *l; |
| |
5373 const char *msgA; |
| |
5374 const char *msgB; |
| |
5375 char *msg; |
| |
5376 |
| |
5377 GaimConnection *gc = pd->gc; |
| |
5378 |
| |
5379 fields = gaim_request_fields_new(); |
| |
5380 |
| |
5381 g = gaim_request_field_group_new(NULL); |
| |
5382 gaim_request_fields_add_group(fields, g); |
| |
5383 |
| |
5384 f = gaim_request_field_list_new("group", _("Possible Matches")); |
| |
5385 gaim_request_field_list_set_multi_select(f, FALSE); |
| |
5386 gaim_request_field_set_required(f, TRUE); |
| |
5387 |
| |
5388 for(l = result->matches; l; l = l->next) { |
| |
5389 struct mwResolveMatch *match = l->data; |
| |
5390 struct named_id *res = g_new0(struct named_id, 1); |
| |
5391 |
| |
5392 res->id = g_strdup(match->id); |
| |
5393 res->name = g_strdup(match->name); |
| |
5394 |
| |
5395 gaim_request_field_list_add(f, res->name, res); |
| |
5396 } |
| |
5397 |
| |
5398 gaim_request_field_group_add_field(g, f); |
| |
5399 |
| |
5400 msgA = _("Notes Address Book group results"); |
| |
5401 msgB = _("The identifier '%s' may possibly refer to any of the following" |
| |
5402 " Notes Address Book groups. Please select the correct group from" |
| |
5403 " the list below to add it to your buddy list."); |
| |
5404 msg = g_strdup_printf(msgB, result->name); |
| |
5405 |
| |
5406 gaim_request_fields(gc, _("Select Notes Address Book"), |
| |
5407 msgA, msg, fields, |
| |
5408 _("Add Group"), G_CALLBACK(remote_group_multi_cb), |
| |
5409 _("Cancel"), G_CALLBACK(remote_group_multi_cleanup), |
| |
5410 pd); |
| |
5411 |
| |
5412 g_free(msg); |
| |
5413 } |
| |
5414 |
| |
5415 |
| |
5416 static void remote_group_resolved(struct mwServiceResolve *srvc, |
| |
5417 guint32 id, guint32 code, GList *results, |
| |
5418 gpointer b) { |
| |
5419 |
| |
5420 struct mwResolveResult *res = NULL; |
| |
5421 struct mwSession *session; |
| |
5422 struct mwGaimPluginData *pd; |
| |
5423 GaimConnection *gc; |
| |
5424 |
| |
5425 session = mwService_getSession(MW_SERVICE(srvc)); |
| |
5426 g_return_if_fail(session != NULL); |
| |
5427 |
| |
5428 pd = mwSession_getClientData(session); |
| |
5429 g_return_if_fail(pd != NULL); |
| |
5430 |
| |
5431 gc = pd->gc; |
| |
5432 g_return_if_fail(gc != NULL); |
| |
5433 |
| |
5434 if(!code && results) { |
| |
5435 res = results->data; |
| |
5436 |
| |
5437 if(res->matches) { |
| |
5438 remote_group_multi(res, pd); |
| |
5439 return; |
| |
5440 } |
| |
5441 } |
| |
5442 |
| |
5443 if(res && res->name) { |
| |
5444 const char *msgA; |
| |
5445 const char *msgB; |
| |
5446 char *msg; |
| |
5447 |
| |
5448 msgA = _("Unable to add group: group not found"); |
| |
5449 |
| |
5450 msgB = _("The identifier '%s' did not match any Notes Address Book" |
| |
5451 " groups in your Sametime community."); |
| |
5452 msg = g_strdup_printf(msgB, res->name); |
| |
5453 |
| |
5454 gaim_notify_error(gc, _("Unable to add group"), msgA, msg); |
| |
5455 |
| |
5456 g_free(msg); |
| |
5457 } |
| |
5458 } |
| |
5459 |
| |
5460 |
| |
5461 static void remote_group_action_cb(GaimConnection *gc, const char *name) { |
| |
5462 struct mwGaimPluginData *pd; |
| |
5463 struct mwServiceResolve *srvc; |
| |
5464 GList *query; |
| |
5465 enum mwResolveFlag flags; |
| |
5466 guint32 req; |
| |
5467 |
| |
5468 pd = gc->proto_data; |
| |
5469 srvc = pd->srvc_resolve; |
| |
5470 |
| |
5471 query = g_list_prepend(NULL, (char *) name); |
| |
5472 flags = mwResolveFlag_FIRST | mwResolveFlag_GROUPS; |
| |
5473 |
| |
5474 req = mwServiceResolve_resolve(srvc, query, flags, remote_group_resolved, |
| |
5475 NULL, NULL); |
| |
5476 g_list_free(query); |
| |
5477 |
| |
5478 if(req == SEARCH_ERROR) { |
| |
5479 /** @todo display error */ |
| |
5480 } |
| |
5481 } |
| |
5482 |
| |
5483 |
| |
5484 static void remote_group_action(GaimPluginAction *act) { |
| |
5485 GaimConnection *gc; |
| |
5486 const char *msgA; |
| |
5487 const char *msgB; |
| |
5488 |
| |
5489 gc = act->context; |
| |
5490 |
| |
5491 msgA = _("Notes Address Book Group"); |
| |
5492 msgB = _("Enter the name of a Notes Address Book group in the field below" |
| |
5493 " to add the group and its members to your buddy list."); |
| |
5494 |
| |
5495 gaim_request_input(gc, _("Add Group"), msgA, msgB, NULL, |
| |
5496 FALSE, FALSE, NULL, |
| |
5497 _("Add"), G_CALLBACK(remote_group_action_cb), |
| |
5498 _("Cancel"), NULL, |
| |
5499 gc); |
| |
5500 } |
| |
5501 |
| |
5502 |
| |
5503 static void search_notify(struct mwResolveResult *result, |
| |
5504 GaimConnection *gc) { |
| |
5505 GList *l; |
| |
5506 const char *msgA; |
| |
5507 const char *msgB; |
| |
5508 char *msg1; |
| |
5509 char *msg2; |
| |
5510 |
| |
5511 GaimNotifySearchResults *sres; |
| |
5512 GaimNotifySearchColumn *scol; |
| |
5513 |
| |
5514 sres = gaim_notify_searchresults_new(); |
| |
5515 |
| |
5516 scol = gaim_notify_searchresults_column_new(_("User Name")); |
| |
5517 gaim_notify_searchresults_column_add(sres, scol); |
| |
5518 |
| |
5519 scol = gaim_notify_searchresults_column_new(_("Sametime ID")); |
| |
5520 gaim_notify_searchresults_column_add(sres, scol); |
| |
5521 |
| |
5522 gaim_notify_searchresults_button_add(sres, GAIM_NOTIFY_BUTTON_IM, |
| |
5523 notify_im); |
| |
5524 |
| |
5525 gaim_notify_searchresults_button_add(sres, GAIM_NOTIFY_BUTTON_ADD, |
| |
5526 notify_add); |
| |
5527 |
| |
5528 for(l = result->matches; l; l = l->next) { |
| |
5529 struct mwResolveMatch *match = l->data; |
| |
5530 GList *row = NULL; |
| |
5531 |
| |
5532 if(!match->id || !match->name) |
| |
5533 continue; |
| |
5534 |
| |
5535 row = g_list_append(row, g_strdup(match->name)); |
| |
5536 row = g_list_append(row, g_strdup(match->id)); |
| |
5537 gaim_notify_searchresults_row_add(sres, row); |
| |
5538 } |
| |
5539 |
| |
5540 msgA = _("Search results for '%s'"); |
| |
5541 msgB = _("The identifier '%s' may possibly refer to any of the following" |
| |
5542 " users. You may add these users to your buddy list or send them" |
| |
5543 " messages with the action buttons below."); |
| |
5544 |
| |
5545 msg1 = g_strdup_printf(msgA, result->name); |
| |
5546 msg2 = g_strdup_printf(msgB, result->name); |
| |
5547 |
| |
5548 gaim_notify_searchresults(gc, _("Search Results"), |
| |
5549 msg1, msg2, sres, notify_close, NULL); |
| |
5550 |
| |
5551 g_free(msg1); |
| |
5552 g_free(msg2); |
| |
5553 } |
| |
5554 |
| |
5555 |
| |
5556 static void search_resolved(struct mwServiceResolve *srvc, |
| |
5557 guint32 id, guint32 code, GList *results, |
| |
5558 gpointer b) { |
| |
5559 |
| |
5560 GaimConnection *gc = b; |
| |
5561 struct mwResolveResult *res = NULL; |
| |
5562 |
| |
5563 if(results) res = results->data; |
| |
5564 |
| |
5565 if(!code && res && res->matches) { |
| |
5566 search_notify(res, gc); |
| |
5567 |
| |
5568 } else { |
| |
5569 const char *msgA; |
| |
5570 const char *msgB; |
| |
5571 char *msg; |
| |
5572 |
| |
5573 msgA = _("No matches"); |
| |
5574 msgB = _("The identifier '%s' did not match any users in your" |
| |
5575 " Sametime community."); |
| |
5576 msg = g_strdup_printf(msgB, NSTR(res->name)); |
| |
5577 |
| |
5578 gaim_notify_error(gc, _("No Matches"), msgA, msg); |
| |
5579 |
| |
5580 g_free(msg); |
| |
5581 } |
| |
5582 } |
| |
5583 |
| |
5584 |
| |
5585 static void search_action_cb(GaimConnection *gc, const char *name) { |
| |
5586 struct mwGaimPluginData *pd; |
| |
5587 struct mwServiceResolve *srvc; |
| |
5588 GList *query; |
| |
5589 enum mwResolveFlag flags; |
| |
5590 guint32 req; |
| |
5591 |
| |
5592 pd = gc->proto_data; |
| |
5593 srvc = pd->srvc_resolve; |
| |
5594 |
| |
5595 query = g_list_prepend(NULL, (char *) name); |
| |
5596 flags = mwResolveFlag_FIRST | mwResolveFlag_USERS; |
| |
5597 |
| |
5598 req = mwServiceResolve_resolve(srvc, query, flags, search_resolved, |
| |
5599 gc, NULL); |
| |
5600 g_list_free(query); |
| |
5601 |
| |
5602 if(req == SEARCH_ERROR) { |
| |
5603 /** @todo display error */ |
| |
5604 } |
| |
5605 } |
| |
5606 |
| |
5607 |
| |
5608 static void search_action(GaimPluginAction *act) { |
| |
5609 GaimConnection *gc; |
| |
5610 const char *msgA; |
| |
5611 const char *msgB; |
| |
5612 |
| |
5613 gc = act->context; |
| |
5614 |
| |
5615 msgA = _("Search for a user"); |
| |
5616 msgB = _("Enter a name or partial ID in the field below to search" |
| |
5617 " for matching users in your Sametime community."); |
| |
5618 |
| |
5619 gaim_request_input(gc, _("User Search"), msgA, msgB, NULL, |
| |
5620 FALSE, FALSE, NULL, |
| |
5621 _("Search"), G_CALLBACK(search_action_cb), |
| |
5622 _("Cancel"), NULL, |
| |
5623 gc); |
| |
5624 } |
| |
5625 |
| |
5626 |
| |
5627 static GList *mw_plugin_actions(GaimPlugin *plugin, gpointer context) { |
| |
5628 GaimPluginAction *act; |
| |
5629 GList *l = NULL; |
| |
5630 |
| |
5631 act = gaim_plugin_action_new(_("Import Sametime List..."), |
| |
5632 st_import_action); |
| |
5633 l = g_list_append(l, act); |
| |
5634 |
| |
5635 act = gaim_plugin_action_new(_("Export Sametime List..."), |
| |
5636 st_export_action); |
| |
5637 l = g_list_append(l, act); |
| |
5638 |
| |
5639 act = gaim_plugin_action_new(_("Add Notes Address Book Group..."), |
| |
5640 remote_group_action); |
| |
5641 l = g_list_append(l, act); |
| |
5642 |
| |
5643 act = gaim_plugin_action_new(_("User Search..."), |
| |
5644 search_action); |
| |
5645 l = g_list_append(l, act); |
| |
5646 |
| |
5647 return l; |
| |
5648 } |
| |
5649 |
| |
5650 |
| |
5651 static gboolean mw_plugin_load(GaimPlugin *plugin) { |
| |
5652 return TRUE; |
| |
5653 } |
| |
5654 |
| |
5655 |
| |
5656 static gboolean mw_plugin_unload(GaimPlugin *plugin) { |
| |
5657 return TRUE; |
| |
5658 } |
| |
5659 |
| |
5660 |
| |
5661 static void mw_plugin_destroy(GaimPlugin *plugin) { |
| |
5662 g_log_remove_handler(G_LOG_DOMAIN, log_handler[0]); |
| |
5663 g_log_remove_handler("meanwhile", log_handler[1]); |
| |
5664 } |
| |
5665 |
| |
5666 |
| |
5667 static GaimPluginInfo mw_plugin_info = { |
| |
5668 .magic = GAIM_PLUGIN_MAGIC, |
| |
5669 .major_version = GAIM_MAJOR_VERSION, |
| |
5670 .minor_version = GAIM_MINOR_VERSION, |
| |
5671 .type = GAIM_PLUGIN_PROTOCOL, |
| |
5672 .ui_requirement = NULL, |
| |
5673 .flags = 0, |
| |
5674 .dependencies = NULL, |
| |
5675 .priority = GAIM_PRIORITY_DEFAULT, |
| |
5676 .id = PLUGIN_ID, |
| |
5677 .name = PLUGIN_NAME, |
| |
5678 .version = VERSION, |
| |
5679 .summary = PLUGIN_SUMMARY, |
| |
5680 .description = PLUGIN_DESC, |
| |
5681 .author = PLUGIN_AUTHOR, |
| |
5682 .homepage = PLUGIN_HOMEPAGE, |
| |
5683 .load = mw_plugin_load, |
| |
5684 .unload = mw_plugin_unload, |
| |
5685 .destroy = mw_plugin_destroy, |
| |
5686 .ui_info = NULL, |
| |
5687 .extra_info = &mw_prpl_info, |
| |
5688 .prefs_info = &mw_plugin_ui_info, |
| |
5689 .actions = mw_plugin_actions, |
| |
5690 }; |
| |
5691 |
| |
5692 |
| |
5693 static void mw_log_handler(const gchar *domain, GLogLevelFlags flags, |
| |
5694 const gchar *msg, gpointer data) { |
| |
5695 |
| |
5696 if(! (msg && *msg)) return; |
| |
5697 |
| |
5698 /* handle g_log requests via gaim's built-in debug logging */ |
| |
5699 if(flags & G_LOG_LEVEL_ERROR) { |
| |
5700 gaim_debug_error(domain, "%s\n", msg); |
| |
5701 |
| |
5702 } else if(flags & G_LOG_LEVEL_WARNING) { |
| |
5703 gaim_debug_warning(domain, "%s\n", msg); |
| |
5704 |
| |
5705 } else { |
| |
5706 gaim_debug_info(domain, "%s\n", msg); |
| |
5707 } |
| |
5708 } |
| |
5709 |
| |
5710 |
| |
5711 static void mw_plugin_init(GaimPlugin *plugin) { |
| |
5712 GaimAccountOption *opt; |
| |
5713 GList *l = NULL; |
| |
5714 |
| |
5715 GLogLevelFlags logflags = |
| |
5716 G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION; |
| |
5717 |
| |
5718 /* set up the preferences */ |
| |
5719 gaim_prefs_add_none(MW_PRPL_OPT_BASE); |
| |
5720 gaim_prefs_add_int(MW_PRPL_OPT_BLIST_ACTION, BLIST_CHOICE_DEFAULT); |
| |
5721 |
| |
5722 /* remove dead preferences */ |
| |
5723 gaim_prefs_remove(MW_PRPL_OPT_PSYCHIC); |
| |
5724 gaim_prefs_remove(MW_PRPL_OPT_SAVE_DYNAMIC); |
| |
5725 |
| |
5726 /* host to connect to */ |
| |
5727 opt = gaim_account_option_string_new(_("Server"), MW_KEY_HOST, |
| |
5728 MW_PLUGIN_DEFAULT_HOST); |
| |
5729 l = g_list_append(l, opt); |
| |
5730 |
| |
5731 /* port to connect to */ |
| |
5732 opt = gaim_account_option_int_new(_("Port"), MW_KEY_PORT, |
| |
5733 MW_PLUGIN_DEFAULT_PORT); |
| |
5734 l = g_list_append(l, opt); |
| |
5735 |
| |
5736 { /* copy the old force login setting from prefs if it's |
| |
5737 there. Don't delete the preference, since there may be more |
| |
5738 than one account that wants to check for it. */ |
| |
5739 gboolean b = FALSE; |
| |
5740 const char *label = _("Force login (ignore server redirects)"); |
| |
5741 |
| |
5742 if(gaim_prefs_exists(MW_PRPL_OPT_FORCE_LOGIN)) |
| |
5743 b = gaim_prefs_get_bool(MW_PRPL_OPT_FORCE_LOGIN); |
| |
5744 |
| |
5745 opt = gaim_account_option_bool_new(label, MW_KEY_FORCE, b); |
| |
5746 l = g_list_append(l, opt); |
| |
5747 } |
| |
5748 |
| |
5749 /* pretend to be Sametime Connect */ |
| |
5750 opt = gaim_account_option_bool_new(_("Hide client identity"), |
| |
5751 MW_KEY_FAKE_IT, FALSE); |
| |
5752 l = g_list_append(l, opt); |
| |
5753 |
| |
5754 mw_prpl_info.protocol_options = l; |
| |
5755 l = NULL; |
| |
5756 |
| |
5757 /* forward all our g_log messages to gaim. Generally all the logging |
| |
5758 calls are using gaim_log directly, but the g_return macros will |
| |
5759 get caught here */ |
| |
5760 log_handler[0] = g_log_set_handler(G_LOG_DOMAIN, logflags, |
| |
5761 mw_log_handler, NULL); |
| |
5762 |
| |
5763 /* redirect meanwhile's logging to gaim's */ |
| |
5764 log_handler[1] = g_log_set_handler("meanwhile", logflags, |
| |
5765 mw_log_handler, NULL); |
| |
5766 } |
| |
5767 |
| |
5768 |
| |
5769 GAIM_INIT_PLUGIN(sametime, mw_plugin_init, mw_plugin_info); |
| |
5770 /* The End. */ |
| |
5771 |