comparison libpurple/protocols/sametime/sametime.c @ 15373:5fe8042783c1

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