src/protocols/sametime/sametime.c

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

mercurial