comparison libgaim/protocols/sametime/sametime.c @ 14192:60b1bc8dbf37

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