libpurple/protocols/sametime/sametime.c

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

mercurial