# HG changeset patch # User Christopher O'Brien # Date 1118112759 0 # Node ID 2ce8ec01a0645d00efb0373f40212993c5d8ac04 # Parent be22eb8fa67125d48ebf46f1e06985804f1ebba3 [gaim-migrate @ 12803] adding sametime support to the build committer: Tailor Script diff -r be22eb8fa671 -r 2ce8ec01a064 configure.ac --- a/configure.ac Mon Jun 06 22:38:11 2005 +0000 +++ b/configure.ac Tue Jun 07 02:52:39 2005 +0000 @@ -153,7 +153,7 @@ fi if test "x$STATIC_PRPLS" = "xall" ; then - STATIC_PRPLS="gg irc jabber msn napster novell oscar silc yahoo zephyr" + STATIC_PRPLS="gg irc jabber msn napster novell oscar sametime silc yahoo zephyr" fi if test "x$silcincludes" != "xyes" -o "x$silcclient" != "xyes"; then STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/silc//'` @@ -175,6 +175,7 @@ novell) static_novell=yes ;; oscar) static_oscar=yes ;; rendezvous) static_rendezvous=yes ;; + sametime) static_sametime=yes ;; silc) static_silc=yes ;; toc) static_toc=yes ;; trepia) static_trepia=yes ;; @@ -191,6 +192,7 @@ AM_CONDITIONAL(STATIC_NOVELL, test "x$static_novell" = "xyes") AM_CONDITIONAL(STATIC_OSCAR, test "x$static_oscar" = "xyes") AM_CONDITIONAL(STATIC_RENDEZVOUS, test "x$static_rendezvous" = "xyes") +AM_CONDITIONAL(STATIC_SAMETIME, test "x$static_sametime" = "xyes") AM_CONDITIONAL(STATIC_SILC, test "x$static_silc" = "xyes" -a "x$silcincludes" = "xyes" -a "x$silcclient" = "xyes") AM_CONDITIONAL(STATIC_TOC, test "x$static_toc" = "xyes") AM_CONDITIONAL(STATIC_TREPIA, test "x$static_trepia" = "xyes") @@ -202,7 +204,7 @@ AC_ARG_WITH(dynamic_prpls, [ --with-dynamic-prpls specify which protocols to build dynamically],[DYNAMIC_PRPLS=`echo $withval | $sedpath 's/,/ /g'`]) if test "x$DYNAMIC_PRPLS" = "xall" ; then - DYNAMIC_PRPLS="gg irc jabber msn napster novell oscar silc yahoo zephyr" + DYNAMIC_PRPLS="gg irc jabber msn napster novell oscar sametime silc yahoo zephyr" fi if test "x$silcincludes" != "xyes" -o "x$silcclient" != "xyes"; then DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/silc//'` @@ -218,6 +220,7 @@ novell) dynamic_novell=yes ;; oscar) dynamic_oscar=yes ;; rendezvous) dynamic_rendezvous=yes ;; + sametime) dynamic_sametime=yes ;; silc) dynamic_silc=yes ;; toc) dynamic_toc=yes ;; trepia) dynamic_trepia=yes ;; @@ -234,6 +237,7 @@ AM_CONDITIONAL(DYNAMIC_NOVELL, test "x$dynamic_novell" = "xyes") AM_CONDITIONAL(DYNAMIC_OSCAR, test "x$dynamic_oscar" = "xyes") AM_CONDITIONAL(DYNAMIC_RENDEZVOUS, test "x$dynamic_rendezvous" = "xyes") +AM_CONDITIONAL(DYNAMIC_SAMETIME, test "x$dynamic_sametime" = "xyes") AM_CONDITIONAL(DYNAMIC_SILC, test "x$dynamic_silc" = "xyes" -a "x$silcincludes" = "xyes" -a "x$silcclient" = "xyes") AM_CONDITIONAL(DYNAMIC_TOC, test "x$dynamic_toc" = "xyes") AM_CONDITIONAL(DYNAMIC_TREPIA, test "x$dynamic_trepia" = "xyes") @@ -1247,6 +1251,7 @@ src/protocols/novell/Makefile src/protocols/oscar/Makefile src/protocols/rendezvous/Makefile + src/protocols/sametime/Makefile src/protocols/silc/Makefile src/protocols/toc/Makefile src/protocols/trepia/Makefile diff -r be22eb8fa671 -r 2ce8ec01a064 gaim.spec.in --- a/gaim.spec.in Mon Jun 06 22:38:11 2005 +0000 +++ b/gaim.spec.in Tue Jun 07 02:52:39 2005 +0000 @@ -59,9 +59,9 @@ %description Gaim allows you to talk to anyone using a variety of messaging -protocols, including AIM, ICQ, IRC, Yahoo!, Novell Groupwise, -MSN Messenger, Jabber, Gadu-Gadu, Napster, and Zephyr. These -protocols are implemented using a modular, easy to use design. +protocols, including AIM, ICQ, IRC, Yahoo!, Novell Groupwise, MSN +Messenger, Jabber, Gadu-Gadu, Napster, Lotus Sametime and Zephyr. +These protocols are implemented using a modular, easy to use design. To use a protocol, just add an account using the account editor. Gaim supports many common features of other clients, as well as many diff -r be22eb8fa671 -r 2ce8ec01a064 pixmaps/status/default/Makefile.am --- a/pixmaps/status/default/Makefile.am Mon Jun 06 22:38:11 2005 +0000 +++ b/pixmaps/status/default/Makefile.am Tue Jun 07 02:52:39 2005 +0000 @@ -32,6 +32,7 @@ offline.png \ op.png \ rendezvous.png \ + sametime.png \ secure.png \ silc.png \ trepia.png \ diff -r be22eb8fa671 -r 2ce8ec01a064 pixmaps/status/default/sametime.png Binary file pixmaps/status/default/sametime.png has changed diff -r be22eb8fa671 -r 2ce8ec01a064 src/Makefile.am --- a/src/Makefile.am Mon Jun 06 22:38:11 2005 +0000 +++ b/src/Makefile.am Tue Jun 07 02:52:39 2005 +0000 @@ -76,6 +76,7 @@ ft.c \ imgstore.c \ log.c \ + mime.c \ network.c \ notify.c \ plugin.c \ @@ -115,6 +116,7 @@ ft.h \ imgstore.h \ log.h \ + mime.h \ network.h \ notify.h \ plugin.h \ diff -r be22eb8fa671 -r 2ce8ec01a064 src/protocols/Makefile.am --- a/src/protocols/Makefile.am Mon Jun 06 22:38:11 2005 +0000 +++ b/src/protocols/Makefile.am Tue Jun 07 02:52:39 2005 +0000 @@ -1,3 +1,3 @@ -DIST_SUBDIRS = gg irc jabber msn napster novell oscar rendezvous silc toc trepia yahoo zephyr +DIST_SUBDIRS = gg irc jabber msn napster novell oscar rendezvous sametime silc toc trepia yahoo zephyr SUBDIRS = $(DYNAMIC_PRPLS) $(STATIC_PRPLS) diff -r be22eb8fa671 -r 2ce8ec01a064 src/protocols/sametime/.cvsignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/sametime/.cvsignore Tue Jun 07 02:52:39 2005 +0000 @@ -0,0 +1,5 @@ +.libs +*.lo +*.o +Makefile +Makefile.in diff -r be22eb8fa671 -r 2ce8ec01a064 src/protocols/sametime/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/sametime/Makefile.am Tue Jun 07 02:52:39 2005 +0000 @@ -0,0 +1,81 @@ + + +EXTRA_DIST = # Makefile.mingw + + +pkgdir = $(libdir)/gaim + + +noinst_HEADERS = \ + meanwhile/mw_channel.h \ + meanwhile/mw_cipher.h \ + meanwhile/mw_common.h \ + meanwhile/mw_debug.h \ + meanwhile/mw_error.h \ + meanwhile/mw_message.h \ + meanwhile/mw_service.h \ + meanwhile/mw_session.h \ + meanwhile/mw_srvc_aware.h \ + meanwhile/mw_srvc_conf.h \ + meanwhile/mw_srvc_ft.h \ + meanwhile/mw_srvc_im.h \ + meanwhile/mw_srvc_resolve.h \ + meanwhile/mw_srvc_store.h \ + meanwhile/mw_st_list.h \ + meanwhile/mw_util.h \ + sametime.h + +meanwhile_SOURCES = \ + meanwhile/channel.c \ + meanwhile/cipher.c \ + meanwhile/common.c \ + meanwhile/error.c \ + meanwhile/message.c \ + meanwhile/mw_debug.c \ + meanwhile/mw_util.c \ + meanwhile/service.c \ + meanwhile/session.c \ + meanwhile/srvc_aware.c \ + meanwhile/srvc_conf.c \ + meanwhile/srvc_ft.c \ + meanwhile/srvc_im.c \ + meanwhile/srvc_store.c \ + meanwhile/srvc_resolve.c \ + meanwhile/st_list.c + +sametime_SOURCES = \ + $(meanwhile_SOURCES) \ + sametime.c + + +if STATIC_SAMETIME + +st = -DGAIM_STATIC_PRPL +noinst_LIBRARIES = libsametime.a +libsametime_a_SOURCES = $(sametime_SOURCES) +libsametime_a_CFLAGS = $(AM_CFLAGS) + +else + +st = +pkg_LTLIBRARIES = libsametime.la +libsametime_la_SOURCES = $(sametime_SOURCES) + +endif + + +libsametime_la_LDFLAGS = -module -avoid-version -no-undefined +libsametime_la_LIBADD = $(GLIB_LIBS) + + +AM_CFLAGS = \ + $(GLIB_CFLAGS) \ + $(DEBUG_CFLAGS) \ + -I$(top_srcdir)/src \ + -Imeanwhile + + +AM_CPPFLAGS = \ + -DG_LOG_DOMAIN=\"meanwhile\" \ + $(st) + diff -r be22eb8fa671 -r 2ce8ec01a064 src/protocols/sametime/meanwhile/mw_srvc_dir.h --- a/src/protocols/sametime/meanwhile/mw_srvc_dir.h Mon Jun 06 22:38:11 2005 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,202 +0,0 @@ -/* - Meanwhile - Unofficial Lotus Sametime Community Client Library - Copyright (C) 2004 Christopher (siege) O'Brien - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with this library; if not, write to the Free - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef _MW_SRVC_DIR_H -#define _MW_SERV_DIR_H - - -#include -#include - - -struct mwSession; - - -#define SERVICE_DIRECTORY 0x0000001a - - -/** @struct mwServiceDirectory - - the directory service. */ -struct mwServiceDirectory; - - -/** @struct mwAddressBook - - server-side collection of users and groups. Open a directory - based on an address book to search or list its contents */ -struct mwAddressBook; - - -/** @struct mwDirectory - - searchable directory, based off of an address book */ -struct mwDirectory; - - -enum mwDirectoryState { - mwDirectory_NEW, /**< directory is created, but not open */ - mwDirectory_PENDING, /**< directory has in the process of opening */ - mwDirectory_OPEN, /**< directory is open */ - mwDirectory_ERROR, /**< error opening or using directory */ - mwDirectory_UNKNOWN, /**< error determining directory state */ -}; - - -/** return value of directory searches that fail */ -#define DIR_SEARCH_ERROR 0x00000000 - - -#define MW_DIRECTORY_IS_STATE(dir, state) \ - (mwDirectory_getState(dir) == (state)) - -#define MW_DIRECTORY_IS_NEW(dir) \ - MW_DIRECTORY_IS_STATE((dir), mwDirectory_NEW) - -#define MW_DIRECTORY_IS_PENDING(dir) \ - MW_DIRECTORY_IS_STATE((dir), mwDirectory_PENDING) - -#define MW_DIRECTORY_IS_OPEN(dir) \ - MW_DIRECTORY_IS_STATE((dir), mwDirectory_OPEN) - - -enum mwDirectoryMemberType { - mwDirectoryMember_USER = 0x0000, - mwDirectoryMember_GROUP = 0x0001, -}; - - -struct mwDirectoryMember { - guint16 type; /**< @see mwDirectoryMemberType */ - char *id; /**< proper ID for member */ - char *long_name; /**< full name of member (USER type only) */ - char *short_name; /**< short name of member */ - guint16 foo; /**< unknown */ -}; - - -/** Appropriate function signature for handling directory search results */ -typedef void (*mwSearchHandler) - (struct mwDirectory *dir, - guint32 code, guint32 offset, GList *members); - - -/** handles asynchronous events for a directory service instance */ -struct mwDirectoryHandler { - - /** handle receipt of the address book list from the service. - Initially triggered by mwServiceDirectory_refreshAddressBooks - and at service startup */ - void (*on_book_list)(struct mwServiceDirectory *srvc, GList *books); - - /** triggered when a directory has been successfully opened */ - void (*dir_opened)(struct mwDirectory *dir); - - /** triggered when a directory has been closed */ - void (*dir_closed)(struct mwDirectory *dir, guint32 reason); - - /** optional. called from mwService_free */ - void (*clear)(struct mwServiceDirectory *srvc); -}; - - -/** Allocate a new directory service instance for use with session */ -struct mwServiceDirectory * -mwServiceDirectory_new(struct mwSession *session, - struct mwDirectoryHandler *handler); - - -/** the handler associated with the service at its creation */ -struct mwDirectoryHandler * -mwServiceDirectory_getHandler(struct mwServiceDirectory *srvc); - - -/** most recent list of address books available in service */ -GList *mwServiceDirectory_getAddressBooks(struct mwServiceDirectory *srvc); - - -/** submit a request to obtain an updated list of address books from - service */ -int mwServiceDirectory_refreshAddressBooks(struct mwServiceDirectory *srvc); - - -/** list of directories in the service */ -GList *mwServiceDirectory_getDirectories(struct mwServiceDirectory *srvc); - - -/** list of directories associated with address book. Note that the - returned GList will need to be free'd after use */ -GList *mwAddressBook_getDirectories(struct mwAddressBook *book); - - -/** the name of the address book */ -const char *mwAddressBook_getName(struct mwAddressBook *book); - - -/** allocate a new directory based off the given address book */ -struct mwDirectory *mwDirectory_new(struct mwAddressBook *book); - - -enum mwDirectoryState mwDirectory_getState(struct mwDirectory *dir); - - -/** set client data. If there is an existing clear function, it will - not be called */ -void mwDirectory_setClientData(struct mwDirectory *dir, - gpointer data, GDestroyNotify clear); - - -/** reference associated client data */ -gpointer mwDirectory_getClientData(struct mwDirectory *dir); - - -/** remove and cleanup user data */ -void mwDirectory_removeClientData(struct mwDirectory *dir); - - -/** reference owning service */ -struct mwServiceDirectory *mwDirectory_getService(struct mwDirectory *dir); - - -/** reference owning address book */ -struct mwAddressBook *mwDirectory_getAddressBook(struct mwDirectory *dir); - - -/** initialize a directory. */ -int mwDirectory_open(struct mwDirectory *dir, mwSearchHandler cb); - - -/** continue a search into its next results */ -int mwDirectory_next(struct mwDirectory *dir); - - -/** continue a search into its previous results */ -int mwDirectory_previous(struct mwDirectory *dir); - - -/** initiate a search on an open directory */ -int mwDirectory_search(struct mwDirectory *dir, const char *query); - - -/** close and free the directory, and unassociate it with its owning - address book and service */ -int mwDirectory_destroy(struct mwDirectory *dir); - - -#endif diff -r be22eb8fa671 -r 2ce8ec01a064 src/protocols/sametime/meanwhile/mw_srvc_place.h --- a/src/protocols/sametime/meanwhile/mw_srvc_place.h Mon Jun 06 22:38:11 2005 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ - -/* - Meanwhile - Unofficial Lotus Sametime Community Client Library - Copyright (C) 2004 Christopher (siege) O'Brien - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with this library; if not, write to the Free - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#ifndef _MW_SRVC_PLACE_H -#define _MW_SRVC_PLACE_H - - -#include -#include "mw_common.h" - - -/** Type identifier for the place service */ -#define SERVICE_PLACE 0x80000022 - - -/** @struct mwServicePlace */ -struct mwServicePlace; - - -/** @struct mwPlace */ -struct mwPlace; - - -/** @struct mwPlaceSection */ -struct mwPlaceSection; - - -/** @struct mwPlaceAction */ -struct mwPlaceAction; - - -struct mwPlaceHandler { - -}; - - -struct mwServicePlace *mwServicePlace_new(struct mwSession *session, - struct mwPlaceHandler *handler); - - -struct mwPlace *mwPlace_new(struct mwServicePlace *place, - const char *title, const char *name); - - -int mwPlace_open(struct mwPlace *place); - - -int mwPlace_close(struct mwPlace *place); - - -void mwPlace_free(struct mwPlace *place); - - -const GList *mwPlace_getSections(struct mwPlace *place); diff -r be22eb8fa671 -r 2ce8ec01a064 src/protocols/sametime/meanwhile/srvc_dir.c --- a/src/protocols/sametime/meanwhile/srvc_dir.c Mon Jun 06 22:38:11 2005 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,664 +0,0 @@ - -/* - Meanwhile - Unofficial Lotus Sametime Community Client Library - Copyright (C) 2004 Christopher (siege) O'Brien - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with this library; if not, write to the Free - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include - -#include "mw_channel.h" -#include "mw_common.h" -#include "mw_debug.h" -#include "mw_error.h" -#include "mw_message.h" -#include "mw_service.h" -#include "mw_session.h" -#include "mw_srvc_dir.h" -#include "mw_util.h" - - -#define PROTOCOL_TYPE 0x0000001c -#define PROTOCOL_VER 0x00000005 - - -enum dir_action { - action_list = 0x0000, /**< list address books */ - action_open = 0x0001, /**< open an addressbook as a directory */ - action_close = 0x0002, /**< close a directory */ - action_search = 0x0003, /**< search an open directory */ -}; - - -struct mwServiceDirectory { - struct mwService service; - - struct mwDirectoryHandler *handler; - - struct mwChannel *channel; - - guint32 counter; /**< counter of request IDs */ - GHashTable *requests; /**< map of request ID:directory */ - GHashTable *books; /**< book->name:mwAddressBook */ -}; - - -struct mwAddressBook { - struct mwServiceDirectory *service; - - guint32 id; /**< id or type or something */ - char *name; /**< name of address book */ - GHashTable *dirs; /**< dir->id:mwDirectory */ -}; - - -struct mwDirectory { - struct mwServiceDirectory *service; - struct mwAddressBook *book; - - enum mwDirectoryState state; - - guint32 id; /**< id of directory, assigned by server */ - guint32 search_id; /**< id of current search, from srvc->counter++ */ - - mwSearchHandler handler; - struct mw_datum client_data; -}; - - -#define next_request_id(srvc) ( ++((srvc)->counter) ) - - -static guint32 map_request(struct mwDirectory *dir) { - struct mwServiceDirectory *srvc = dir->service; - guint32 id = next_request_id(srvc); - - dir->search_id = id; - map_guint_insert(srvc->requests, id, dir); - - return id; -} - - -/** called when directory is removed from the service directory map */ -static void dir_free(struct mwDirectory *dir) { - map_guint_remove(dir->service->requests, dir->search_id); - g_free(dir); -} - - -/** remove the directory from the service list and its owning address - book, then frees the directory */ -static void dir_remove(struct mwDirectory *dir) { - struct mwAddressBook *book = dir->book; - map_guint_remove(book->dirs, dir->id); -} - - -__attribute__((used)) -static struct mwDirectory *dir_new(struct mwAddressBook *book, guint32 id) { - struct mwDirectory *dir = g_new0(struct mwDirectory, 1); - dir->service = book->service; - dir->book = book; - dir->id = id; - map_guint_insert(book->dirs, id, dir); - return dir; -} - - -/** called when book is removed from the service book map. Removed all - directories as well */ -static void book_free(struct mwAddressBook *book) { - g_hash_table_destroy(book->dirs); - g_free(book->name); -} - - -__attribute__((used)) -static void book_remove(struct mwAddressBook *book) { - struct mwServiceDirectory *srvc = book->service; - g_hash_table_remove(srvc->books, book->name); -} - - -static struct mwAddressBook *book_new(struct mwServiceDirectory *srvc, - const char *name, guint32 id) { - struct mwAddressBook *book = g_new0(struct mwAddressBook, 1); - book->service = srvc; - book->id = id; - book->name = g_strdup(name); - book->dirs = map_guint_new_full((GDestroyNotify) dir_free); - g_hash_table_insert(srvc->books, book->name, book); - return book; -} - - -static const char *getName(struct mwService *srvc) { - return "Address Book and Directory"; -} - - -static const char *getDesc(struct mwService *srvc) { - return "Address book directory service for user and group lookups"; -} - - -static struct mwChannel *make_channel(struct mwServiceDirectory *srvc) { - struct mwSession *session; - struct mwChannelSet *cs; - struct mwChannel *chan; - - session = mwService_getSession(MW_SERVICE(srvc)); - cs = mwSession_getChannels(session); - chan = mwChannel_newOutgoing(cs); - - mwChannel_setService(chan, MW_SERVICE(srvc)); - mwChannel_setProtoType(chan, PROTOCOL_TYPE); - mwChannel_setProtoVer(chan, PROTOCOL_VER); - - return mwChannel_create(chan)? NULL: chan; -} - - -static void start(struct mwServiceDirectory *srvc) { - struct mwChannel *chan; - - chan = make_channel(srvc); - if(chan) { - srvc->channel = chan; - } else { - mwService_stopped(MW_SERVICE(srvc)); - return; - } -} - - -static void stop(struct mwServiceDirectory *srvc) { - /* XXX */ - - if(srvc->channel) { - mwChannel_destroy(srvc->channel, ERR_SUCCESS, NULL); - srvc->channel = NULL; - } -} - - -static void clear(struct mwServiceDirectory *srvc) { - struct mwDirectoryHandler *handler; - - if(srvc->books) { - g_hash_table_destroy(srvc->books); - srvc->books = NULL; - } - - /* clear the handler */ - handler = srvc->handler; - if(handler && handler->clear) - handler->clear(srvc); - srvc->handler = NULL; -} - - -static void recv_create(struct mwServiceDirectory *srvc, - struct mwChannel *chan, - struct mwMsgChannelCreate *msg) { - - /* no way man, we call the shots around here */ - mwChannel_destroy(chan, ERR_FAILURE, NULL); -} - - -static void recv_accept(struct mwServiceDirectory *srvc, - struct mwChannel *chan, - struct mwMsgChannelAccept *msg) { - - g_return_if_fail(srvc->channel != NULL); - g_return_if_fail(srvc->channel == chan); - - if(MW_SERVICE_IS_STARTING(srvc)) { - mwService_started(MW_SERVICE(srvc)); - - } else { - mwChannel_destroy(chan, ERR_FAILURE, NULL); - } -} - - -static void recv_destroy(struct mwServiceDirectory *srvc, - struct mwChannel *chan, - struct mwMsgChannelDestroy *msg) { - - srvc->channel = NULL; - mwService_stop(MW_SERVICE(srvc)); - /** @todo session sense service */ -} - - -static void recv_list(struct mwServiceDirectory *srvc, - struct mwOpaque *data) { - - struct mwGetBuffer *b; - guint32 request, code, count; - gboolean foo_1; - guint16 foo_2; - - b = mwGetBuffer_wrap(data); - - guint32_get(b, &request); - guint32_get(b, &code); - guint32_get(b, &count); - - gboolean_get(b, &foo_1); - guint16_get(b, &foo_2); - - if(foo_1 || foo_2) { - mw_debug_mailme(data, "received strange address book list"); - mwGetBuffer_free(b); - return; - } - - while(!mwGetBuffer_error(b) && count--) { - guint32 id; - char *name = NULL; - - guint32_get(b, &id); - mwString_get(b, &name); - - book_new(srvc, name, id); - g_free(name); - } -} - - -static void recv_open(struct mwServiceDirectory *srvc, - struct mwOpaque *data) { - - /* look up the directory associated with this request id, - mark it as open, and trigger the event */ -} - - -static void recv_search(struct mwServiceDirectory *srvc, - struct mwOpaque *data) { - - /* look up the directory associated with this request id, - trigger the event */ -} - - -static void recv(struct mwServiceDirectory *srvc, - struct mwChannel *chan, - guint16 msg_type, struct mwOpaque *data) { - - g_return_if_fail(srvc != NULL); - g_return_if_fail(chan != NULL); - g_return_if_fail(chan == srvc->channel); - g_return_if_fail(data != NULL); - - switch(msg_type) { - case action_list: - recv_list(srvc, data); - break; - - case action_open: - recv_open(srvc, data); - break; - - case action_close: - ; /* I don't think we should receive these */ - break; - - case action_search: - recv_search(srvc, data); - break; - - default: - mw_debug_mailme(data, "msg type 0x%04x in directory service", msg_type); - } -} - - -struct mwServiceDirectory * -mwServiceDirectory_new(struct mwSession *session, - struct mwDirectoryHandler *handler) { - - struct mwServiceDirectory *srvc; - struct mwService *service; - - g_return_val_if_fail(session != NULL, NULL); - g_return_val_if_fail(handler != NULL, NULL); - - srvc = g_new0(struct mwServiceDirectory, 1); - service = MW_SERVICE(srvc); - - mwService_init(service, session, SERVICE_DIRECTORY); - service->get_name = getName; - service->get_desc = getDesc; - service->start = (mwService_funcStart) start; - service->stop = (mwService_funcStop) stop; - service->clear = (mwService_funcClear) clear; - service->recv_create = (mwService_funcRecvCreate) recv_create; - service->recv_accept = (mwService_funcRecvAccept) recv_accept; - service->recv_destroy = (mwService_funcRecvDestroy) recv_destroy; - service->recv = (mwService_funcRecv) recv; - - srvc->handler = handler; - srvc->requests = map_guint_new(); - srvc->books = g_hash_table_new_full(g_str_hash, g_str_equal, - NULL, (GDestroyNotify) book_free); - return srvc; -} - - -struct mwDirectoryHandler * -mwServiceDirectory_getHandler(struct mwServiceDirectory *srvc) { - g_return_val_if_fail(srvc != NULL, NULL); - return srvc->handler; -} - - -int mwServiceDirectory_refreshAddressBooks(struct mwServiceDirectory *srvc) { - struct mwChannel *chan; - struct mwPutBuffer *b; - struct mwOpaque o; - int ret; - - g_return_val_if_fail(srvc != NULL, -1); - - chan = srvc->channel; - g_return_val_if_fail(chan != NULL, -1); - - b = mwPutBuffer_new(); - guint32_put(b, next_request_id(srvc)); - - mwPutBuffer_finalize(&o, b); - ret = mwChannel_send(chan, action_list, &o); - mwOpaque_clear(&o); - - return ret; -} - - -GList *mwServiceDirectory_getAddressBooks(struct mwServiceDirectory *srvc) { - g_return_val_if_fail(srvc != NULL, NULL); - g_return_val_if_fail(srvc->books != NULL, NULL); - - return map_collect_values(srvc->books); -} - - -GList *mwServiceDirectory_getDirectories(struct mwServiceDirectory *srvc) { - GList *bl, *ret = NULL; - - g_return_val_if_fail(srvc != NULL, NULL); - g_return_val_if_fail(srvc->books != NULL, NULL); - - bl = map_collect_values(srvc->books); - for( ; bl; bl = g_list_delete_link(bl, bl)) { - struct mwAddressBook *book = bl->data; - ret = g_list_concat(ret, map_collect_values(book->dirs)); - } - - return ret; -} - - -GList *mwAddressBook_getDirectories(struct mwAddressBook *book) { - g_return_val_if_fail(book != NULL, NULL); - g_return_val_if_fail(book->dirs != NULL, NULL); - - return map_collect_values(book->dirs); -} - - -const char *mwAddressBook_getName(struct mwAddressBook *book) { - g_return_val_if_fail(book != NULL, NULL); - return book->name; -} - - -struct mwDirectory *mwDirectory_new(struct mwAddressBook *book) { - struct mwDirectory *dir; - - g_return_val_if_fail(book != NULL, NULL); - g_return_val_if_fail(book->service != NULL, NULL); - - dir = g_new0(struct mwDirectory, 1); - dir->service = book->service; - dir->book = book; - dir->state = mwDirectory_NEW; - - return dir; -} - - -enum mwDirectoryState mwDirectory_getState(struct mwDirectory *dir) { - g_return_val_if_fail(dir != NULL, mwDirectory_UNKNOWN); - return dir->state; -} - - -void mwDirectory_setClientData(struct mwDirectory *dir, - gpointer data, GDestroyNotify clear) { - - g_return_if_fail(dir != NULL); - mw_datum_set(&dir->client_data, data, clear); -} - - -gpointer mwDirectory_getClientData(struct mwDirectory *dir) { - g_return_val_if_fail(dir != NULL, NULL); - return mw_datum_get(&dir->client_data); -} - - -void mwDirectory_removeClientData(struct mwDirectory *dir) { - g_return_if_fail(dir != NULL); - mw_datum_clear(&dir->client_data); -} - - -struct mwServiceDirectory *mwDirectory_getService(struct mwDirectory *dir) { - g_return_val_if_fail(dir != NULL, NULL); - g_return_val_if_fail(dir->book != NULL, NULL); - return dir->book->service; -} - - -struct mwAddressBook *mwDirectory_getAddressBook(struct mwDirectory *dir) { - g_return_val_if_fail(dir != NULL, NULL); - return dir->book; -} - - -static int dir_open(struct mwDirectory *dir) { - struct mwServiceDirectory *srvc; - struct mwChannel *chan; - struct mwPutBuffer *b; - struct mwOpaque o; - int ret; - - g_return_val_if_fail(dir != NULL, -1); - - srvc = dir->service; - g_return_val_if_fail(srvc != NULL, -1); - - chan = srvc->channel; - g_return_val_if_fail(chan != NULL, -1); - - b = mwPutBuffer_new(); - guint32_put(b, map_request(dir)); - - /* unsure about these three bytes */ - gboolean_put(b, FALSE); - guint16_put(b, 0x0000); - - guint32_put(b, dir->book->id); - mwString_put(b, dir->book->name); - - mwPutBuffer_finalize(&o, b); - ret = mwChannel_send(chan, action_open, &o); - mwOpaque_clear(&o); - - return ret; -} - - -int mwDirectory_open(struct mwDirectory *dir, mwSearchHandler cb) { - g_return_val_if_fail(dir != NULL, -1); - g_return_val_if_fail(cb != NULL, -1); - g_return_val_if_fail(MW_DIRECTORY_IS_NEW(dir), -1); - - dir->state = mwDirectory_PENDING; - dir->handler = cb; - - return dir_open(dir); -} - - -int mwDirectory_next(struct mwDirectory *dir) { - struct mwServiceDirectory *srvc; - struct mwChannel *chan; - struct mwPutBuffer *b; - struct mwOpaque o; - int ret; - - g_return_val_if_fail(dir != NULL, -1); - g_return_val_if_fail(MW_DIRECTORY_IS_OPEN(dir), -1); - - srvc = dir->service; - g_return_val_if_fail(srvc != NULL, -1); - - chan = srvc->channel; - g_return_val_if_fail(chan != NULL, -1); - - b = mwPutBuffer_new(); - guint32_put(b, map_request(dir)); - guint32_put(b, dir->id); - guint16_put(b, 0xffff); /* some magic? */ - guint32_put(b, 0x00000000); /* next results */ - - mwPutBuffer_finalize(&o, b); - ret = mwChannel_send(chan, action_search, &o); - mwOpaque_clear(&o); - - return ret; -} - - -int mwDirectory_previous(struct mwDirectory *dir) { - struct mwServiceDirectory *srvc; - struct mwChannel *chan; - struct mwPutBuffer *b; - struct mwOpaque o; - int ret; - - g_return_val_if_fail(dir != NULL, -1); - g_return_val_if_fail(MW_DIRECTORY_IS_OPEN(dir), -1); - - srvc = dir->service; - g_return_val_if_fail(srvc != NULL, -1); - - chan = srvc->channel; - g_return_val_if_fail(chan != NULL, -1); - - b = mwPutBuffer_new(); - guint32_put(b, map_request(dir)); - guint32_put(b, dir->id); - guint16_put(b, 0x0061); /* some magic? */ - guint32_put(b, 0x00000001); /* prev results */ - - mwPutBuffer_finalize(&o, b); - ret = mwChannel_send(chan, action_search, &o); - mwOpaque_clear(&o); - - return ret; -} - - -int mwDirectory_search(struct mwDirectory *dir, const char *query) { - struct mwServiceDirectory *srvc; - struct mwChannel *chan; - struct mwPutBuffer *b; - struct mwOpaque o; - int ret; - - g_return_val_if_fail(dir != NULL, -1); - g_return_val_if_fail(MW_DIRECTORY_IS_OPEN(dir), -1); - g_return_val_if_fail(query != NULL, -1); - g_return_val_if_fail(*query != '\0', -1); - - srvc = dir->service; - g_return_val_if_fail(srvc != NULL, -1); - - chan = srvc->channel; - g_return_val_if_fail(chan != NULL, -1); - - b = mwPutBuffer_new(); - guint32_put(b, map_request(dir)); - guint32_put(b, dir->id); - guint16_put(b, 0x0061); /* some magic? */ - guint32_put(b, 0x00000008); /* seek results */ - mwString_put(b, query); - - mwPutBuffer_finalize(&o, b); - ret = mwChannel_send(chan, action_search, &o); - mwOpaque_clear(&o); - - return ret; -} - - -static int dir_close(struct mwDirectory *dir) { - struct mwServiceDirectory *srvc; - struct mwChannel *chan; - struct mwPutBuffer *b; - struct mwOpaque o; - int ret; - - g_return_val_if_fail(dir != NULL, -1); - - srvc = dir->service; - g_return_val_if_fail(srvc != NULL, -1); - - chan = srvc->channel; - g_return_val_if_fail(chan != NULL, -1); - - b = mwPutBuffer_new(); - guint32_put(b, next_request_id(dir->service)); - guint32_put(b, dir->id); - - mwPutBuffer_finalize(&o, b); - ret = mwChannel_send(chan, action_close, &o); - mwOpaque_clear(&o); - - return ret; -} - - -int mwDirectory_destroy(struct mwDirectory *dir) { - int ret = 0; - - g_return_val_if_fail(dir != NULL, -1); - - if(MW_DIRECTORY_IS_OPEN(dir) || MW_DIRECTORY_IS_PENDING(dir)) { - ret = dir_close(dir); - } - dir_remove(dir); - - return ret; -} - diff -r be22eb8fa671 -r 2ce8ec01a064 src/protocols/sametime/meanwhile/srvc_ft.c --- a/src/protocols/sametime/meanwhile/srvc_ft.c Mon Jun 06 22:38:11 2005 +0000 +++ b/src/protocols/sametime/meanwhile/srvc_ft.c Tue Jun 07 02:52:39 2005 +0000 @@ -538,7 +538,7 @@ int mwFileTransfer_close(struct mwFileTransfer *ft, guint32 code) { struct mwServiceFileTransfer *srvc; struct mwFileTransferHandler *handler; - int ret; + int ret = 0; g_return_val_if_fail(ft != NULL, -1); diff -r be22eb8fa671 -r 2ce8ec01a064 src/protocols/sametime/sametime.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/sametime/sametime.c Tue Jun 07 02:52:39 2005 +0000 @@ -0,0 +1,5073 @@ + +/* + Meanwhile Protocol Plugin for Gaim + Adds Lotus Sametime support to Gaim using the Meanwhile library + + Copyright (C) 2004 Christopher (siege) O'Brien + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at + your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sametime.h" + + +/* considering that there's no display of this information for prpls, + I don't know why I even bother providing these. Oh valiant reader, + I do it all for you. */ +/* scratch that, I just added it to the prpl options panel */ +#define PLUGIN_ID "prpl-meanwhile" +#define PLUGIN_NAME "Sametime" +#define PLUGIN_SUMMARY "Sametime Protocol Plugin" +#define PLUGIN_DESC "Open implementation of a Lotus Sametime client" +#define PLUGIN_AUTHOR "Christopher (siege) O'Brien " +#define PLUGIN_HOMEPAGE "http://meanwhile.sourceforge.net/" + + +/* plugin preference names */ +#define MW_PRPL_OPT_BASE "/plugins/prpl/meanwhile" +#define MW_PRPL_OPT_BLIST_ACTION MW_PRPL_OPT_BASE "/blist_action" +#define MW_PRPL_OPT_PSYCHIC MW_PRPL_OPT_BASE "/psychic" +#define MW_PRPL_OPT_FORCE_LOGIN MW_PRPL_OPT_BASE "/force_login" +#define MW_PRPL_OPT_SAVE_DYNAMIC MW_PRPL_OPT_BASE "/save_dynamic" + + +/* stages of connecting-ness */ +#define MW_CONNECT_STEPS 9 + + +/* stages of conciousness */ +#define MW_STATE_OFFLINE "offline" +#define MW_STATE_ONLINE "online" +#define MW_STATE_ACTIVE "available" +#define MW_STATE_AWAY "away" +#define MW_STATE_BUSY "busy" +#define MW_STATE_IDLE "idle" +#define MW_STATE_UNKNOWN "unknown" +#define MW_STATE_BUDHA "enlightened" + +#define MW_STATE_MESSAGE "message" + + +/* keys to get/set chat information */ +#define CHAT_KEY_CREATOR "chat.creator" +#define CHAT_KEY_NAME "chat.name" +#define CHAT_KEY_TOPIC "chat.topic" +#define CHAT_KEY_INVITE "chat.invite" + + +/* key for associating a mwLoginType with a buddy */ +#define BUDDY_KEY_CLIENT "meanwhile.client" + +/* store the remote alias so that we can re-create it easily */ +#define BUDDY_KEY_NAME "meanwhile.shortname" + +/* enum mwSametimeUserType */ +#define BUDDY_KEY_TYPE "meanwhile.type" + + +/* key for the real group name for a meanwhile group */ +#define GROUP_KEY_NAME "meanwhile.group" + +/* enum mwSametimeGroupType */ +#define GROUP_KEY_TYPE "meanwhile.type" + +/* NAB group owning account */ +#define GROUP_KEY_OWNER "meanwhile.account" + +/* key gtk blist uses to indicate a collapsed group */ +#define GROUP_KEY_COLLAPSED "collapsed" + + +#define SESSION_NO_SECRET "meanwhile.no_secret" + + +/* keys to get/set gaim plugin information */ +#define MW_KEY_HOST "server" +#define MW_KEY_PORT "port" +#define MW_KEY_ACTIVE_MSG "active_msg" +#define MW_KEY_AWAY_MSG "away_msg" +#define MW_KEY_BUSY_MSG "busy_msg" +#define MW_KEY_MSG_PROMPT "msg_prompt" +#define MW_KEY_INVITE "conf_invite" + + +/** number of seconds from the first blist change before a save to the + storage service occurs. */ +#define BLIST_SAVE_SECONDS 15 + + +/** blist storage option, local only */ +#define BLIST_CHOICE_NONE 1 + +/** blist storage option, load from server */ +#define BLIST_CHOICE_LOAD 2 + +/** blist storage option, load and save to server */ +#define BLIST_CHOICE_SAVE 3 + +/** blist storage option, server only */ +#define BLIST_CHOICE_SERVER 4 + + +/** the default blist storage option */ +#define BLIST_CHOICE_DEFAULT BLIST_CHOICE_SAVE + + +/* testing for the above */ +#define BLIST_CHOICE_IS(n) (gaim_prefs_get_int(MW_PRPL_OPT_BLIST_ACTION)==(n)) +#define BLIST_CHOICE_IS_NONE() BLIST_CHOICE_IS(BLIST_CHOICE_NONE) +#define BLIST_CHOICE_IS_LOAD() BLIST_CHOICE_IS(BLIST_CHOICE_LOAD) +#define BLIST_CHOICE_IS_SAVE() BLIST_CHOICE_IS(BLIST_CHOICE_SAVE) + + +/** warning text placed next to blist option */ +#define BLIST_WARNING \ + ("Please note:\n" \ + "The 'merge and save' option above is still mildly experimental." \ + " You should back-up your buddy list with an official client" \ + " before enabling this option. Loading takes effect at login.") + + +/* debugging output */ +#define DEBUG_ERROR(a...) gaim_debug_error(G_LOG_DOMAIN, a) +#define DEBUG_INFO(a...) gaim_debug_info(G_LOG_DOMAIN, a) +#define DEBUG_MISC(a...) gaim_debug_misc(G_LOG_DOMAIN, a) +#define DEBUG_WARN(a...) gaim_debug_warning(G_LOG_DOMAIN, a) + + +/** ensure non-null strings */ +#ifndef NSTR +# define NSTR(str) ((str)? (str): "(null)") +#endif + + +/** calibrates distinct secure channel nomenclature */ +static const unsigned char no_secret[] = { + 0x2d, 0x2d, 0x20, 0x73, 0x69, 0x65, 0x67, 0x65, + 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x73, 0x20, 0x6a, + 0x65, 0x6e, 0x6e, 0x69, 0x20, 0x61, 0x6e, 0x64, + 0x20, 0x7a, 0x6f, 0x65, 0x20, 0x2d, 0x2d, 0x00, +}; + + +/** handler IDs from g_log_set_handler in mw_plugin_init */ +static guint log_handler[2] = { 0, 0 }; + + +/** the gaim plugin data. + available as gc->proto_data and mwSession_getClientData */ +struct mwGaimPluginData { + struct mwSession *session; + + struct mwServiceAware *srvc_aware; + struct mwServiceConference *srvc_conf; + struct mwServiceFileTransfer *srvc_ft; + struct mwServiceIm *srvc_im; + struct mwServiceResolve *srvc_resolve; + struct mwServiceStorage *srvc_store; + + /** map of GaimGroup:mwAwareList and mwAwareList:GaimGroup */ + GHashTable *group_list_map; + + /** event id for the buddy list save callback */ + guint save_event; + + /** socket fd */ + int socket; + + GaimConnection *gc; +}; + + +/* blist and aware functions */ + +static void blist_export(GaimConnection *gc, struct mwSametimeList *stlist); + +static void blist_store(struct mwGaimPluginData *pd); + +static void blist_schedule(struct mwGaimPluginData *pd); + +static void blist_import(GaimConnection *gc, struct mwSametimeList *stlist); + +static void buddy_add(struct mwGaimPluginData *pd, GaimBuddy *buddy); + +static GaimBuddy * +buddy_ensure(GaimConnection *gc, GaimGroup *group, + struct mwSametimeUser *stuser); + +static void group_add(struct mwGaimPluginData *pd, GaimGroup *group); + +static GaimGroup * +group_ensure(GaimConnection *gc, struct mwSametimeGroup *stgroup); + +static struct mwAwareList * +list_ensure(struct mwGaimPluginData *pd, GaimGroup *group); + + +/* session functions */ + +static struct mwSession * +gc_to_session(GaimConnection *gc); + +static GaimConnection *session_to_gc(struct mwSession *session); + + +/* conference functions */ + +static struct mwConference * +conf_find_by_id(struct mwGaimPluginData *pd, int id); + + +/* conversation functions */ + +struct convo_msg { + enum mwImSendType type; + gpointer data; + GDestroyNotify clear; +}; + + +struct convo_data { + struct mwConversation *conv; + GList *queue; /**< outgoing message queue, list of convo_msg */ +}; + +static void convo_data_new(struct mwConversation *conv); + +static void convo_data_free(struct convo_data *conv); + +static void convo_features(struct mwConversation *conv); + +static GaimConversation *convo_get_gconv(struct mwConversation *conv); + + +/* resolved id */ + +struct resolved_id { + char *id; + char *name; +}; + + +/* ----- session ------ */ + + +/** resolves a mwSession from a GaimConnection */ +static struct mwSession *gc_to_session(GaimConnection *gc) { + struct mwGaimPluginData *pd; + + g_return_val_if_fail(gc != NULL, NULL); + + pd = gc->proto_data; + g_return_val_if_fail(pd != NULL, NULL); + + return pd->session; +} + + +/** resolves a GaimConnection from a mwSession */ +static GaimConnection *session_to_gc(struct mwSession *session) { + struct mwGaimPluginData *pd; + + g_return_val_if_fail(session != NULL, NULL); + + pd = mwSession_getClientData(session); + g_return_val_if_fail(pd != NULL, NULL); + + return pd->gc; +} + + +static int mw_session_io_write(struct mwSession *session, + const char *buf, gsize len) { + struct mwGaimPluginData *pd; + int ret = 0; + + pd = mwSession_getClientData(session); + + /* socket was already closed. */ + if(pd->socket == 0) + return 1; + + while(len) { + ret = write(pd->socket, buf, len); + if(ret <= 0) break; + len -= ret; + } + + if(len > 0) { + DEBUG_ERROR("write returned %i, %i bytes left unwritten\n", ret, len); + gaim_connection_error(pd->gc, "Connection closed (writing)"); + close(pd->socket); + pd->socket = 0; + return -1; + } + + return 0; +} + + +static void mw_session_io_close(struct mwSession *session) { + struct mwGaimPluginData *pd; + + pd = mwSession_getClientData(session); + if(pd->socket) { + close(pd->socket); + pd->socket = 0; + } +} + + +static void mw_session_clear(struct mwSession *session) { + ; /* nothing for now */ +} + + +/* ----- aware list ----- */ + + +static void blist_resolve_alias_cb(struct mwServiceResolve *srvc, + guint32 id, guint32 code, GList *results, + gpointer data) { + struct mwResolveResult *result; + struct mwResolveMatch *match; + + g_return_if_fail(results != NULL); + + result = results->data; + g_return_if_fail(result != NULL); + g_return_if_fail(result->matches != NULL); + + match = result->matches->data; + g_return_if_fail(match != NULL); + + gaim_blist_server_alias_buddy(data, match->name); + gaim_blist_node_set_string(data, BUDDY_KEY_NAME, match->name); +} + + +static void mw_aware_list_on_aware(struct mwAwareList *list, + struct mwAwareSnapshot *aware) { + + GaimConnection *gc; + struct mwGaimPluginData *pd; + + time_t idle = 0; + guint stat = aware->status.status; + + const char *id = aware->id.user; + + gc = mwAwareList_getClientData(list); + pd = gc->proto_data; + + switch(stat) { + case mwStatus_IDLE: + idle = -1; + break; + + case mwStatus_AWAY: + case mwStatus_BUSY: + /* need to let gaim know that these are 'unavailable' states */ + + /* XXX */ + /* stat |= UC_UNAVAILABLE; */ + + break; + } + + if(aware->group) { + GaimAccount *acct; + GaimGroup *group; + GaimBuddy *buddy; + GaimBlistNode *bnode; + + acct = gaim_connection_get_account(gc); + group = g_hash_table_lookup(pd->group_list_map, list); + buddy = gaim_find_buddy_in_group(acct, id, group); + bnode = (GaimBlistNode *) buddy; + + if(! buddy) { + struct mwServiceResolve *srvc; + GList *query; + + buddy = gaim_buddy_new(acct, id, NULL); + gaim_blist_add_buddy(buddy, NULL, group, NULL); + + bnode = (GaimBlistNode *) buddy; + + /* mark buddy as transient if preferences do not indicate that + we should save the buddy between gaim sessions */ + if(! gaim_prefs_get_bool(MW_PRPL_OPT_SAVE_DYNAMIC)) + bnode->flags |= GAIM_BLIST_NODE_FLAG_NO_SAVE; + + srvc = pd->srvc_resolve; + query = g_list_append(NULL, (char *) id); + + mwServiceResolve_resolve(srvc, query, mwResolveFlag_USERS, + blist_resolve_alias_cb, buddy, NULL); + } + + gaim_blist_node_set_int(bnode, BUDDY_KEY_TYPE, mwSametimeUser_NORMAL); + } + + /* XXX */ + /* serv_got_update(gc, id, aware->online, 0, 0, idle, stat); */ +} + + +static void mw_aware_list_on_attrib(struct mwAwareList *list, + struct mwAwareIdBlock *id, + struct mwAwareAttribute *attrib) { + + ; /* nothing. We'll get attribute data as we need it */ +} + + +static void mw_aware_list_clear(struct mwAwareList *list) { + ; /* nothing for now */ +} + + +static struct mwAwareListHandler mw_aware_list_handler = { + .on_aware = mw_aware_list_on_aware, + .on_attrib = mw_aware_list_on_attrib, + .clear = mw_aware_list_clear, +}; + + +/** Ensures that an Aware List is associated with the given group, and + returns that list. */ +static struct mwAwareList * +list_ensure(struct mwGaimPluginData *pd, GaimGroup *group) { + + struct mwAwareList *list; + + g_return_val_if_fail(pd != NULL, NULL); + g_return_val_if_fail(group != NULL, NULL); + + list = g_hash_table_lookup(pd->group_list_map, group); + if(! list) { + list = mwAwareList_new(pd->srvc_aware, &mw_aware_list_handler); + mwAwareList_setClientData(list, pd->gc, NULL); + + mwAwareList_watchAttributes(list, + mwAttribute_AV_PREFS_SET, + mwAttribute_MICROPHONE, + mwAttribute_SPEAKERS, + mwAttribute_VIDEO_CAMERA, + mwAttribute_FILE_TRANSFER, + NULL); + + g_hash_table_replace(pd->group_list_map, group, list); + g_hash_table_insert(pd->group_list_map, list, group); + } + + return list; +} + + +static void blist_export(GaimConnection *gc, struct mwSametimeList *stlist) { + /* - find the account for this connection + - iterate through the buddy list + - add each buddy matching this account to the stlist + */ + + GaimAccount *acct; + GaimBuddyList *blist; + GaimBlistNode *gn, *cn, *bn; + GaimGroup *grp; + GaimBuddy *bdy; + + struct mwSametimeGroup *stg = NULL; + struct mwIdBlock idb = { NULL, NULL }; + + acct = gaim_connection_get_account(gc); + g_return_if_fail(acct != NULL); + + blist = gaim_get_blist(); + g_return_if_fail(blist != NULL); + + for(gn = blist->root; gn; gn = gn->next) { + const char *owner; + const char *gname; + enum mwSametimeGroupType gtype; + gboolean gopen; + + if(! GAIM_BLIST_NODE_IS_GROUP(gn)) continue; + grp = (GaimGroup *) gn; + + /* the group's type (normal or dynamic) */ + gtype = gaim_blist_node_get_int(gn, GROUP_KEY_TYPE); + if(! gtype) gtype = mwSametimeGroup_NORMAL; + + /* if it's a normal group with none of our people in it, skip it */ + if(gtype == mwSametimeGroup_NORMAL && !gaim_group_on_account(grp, acct)) + continue; + + /* if the group has an owner and we're not it, skip it */ + owner = gaim_blist_node_get_string(gn, GROUP_KEY_OWNER); + if(owner && strcmp(owner, gaim_account_get_username(acct))) + continue; + + /* the group's actual name may be different from the gaim group's + name. Find whichever is there */ + gname = gaim_blist_node_get_string(gn, GROUP_KEY_NAME); + if(! gname) gname = grp->name; + + /* we save this, but never actually honor it */ + gopen = ! gaim_blist_node_get_bool(gn, GROUP_KEY_COLLAPSED); + + stg = mwSametimeGroup_new(stlist, gtype, gname); + mwSametimeGroup_setAlias(stg, grp->name); + mwSametimeGroup_setOpen(stg, gopen); + + for(cn = gn->child; cn; cn = cn->next) { + if(! GAIM_BLIST_NODE_IS_CONTACT(cn)) continue; + + for(bn = cn->child; bn; bn = bn->next) { + if(! GAIM_BLIST_NODE_IS_BUDDY(bn)) continue; + if(! GAIM_BLIST_NODE_SHOULD_SAVE(bn)) continue; + + bdy = (GaimBuddy *) bn; + + if(bdy->account == acct) { + struct mwSametimeUser *stu; + enum mwSametimeUserType utype; + + idb.user = bdy->name; + + utype = gaim_blist_node_get_int(bn, BUDDY_KEY_TYPE); + if(! utype) utype = mwSametimeUser_NORMAL; + + stu = mwSametimeUser_new(stg, utype, &idb); + mwSametimeUser_setShortName(stu, bdy->server_alias); + mwSametimeUser_setAlias(stu, bdy->alias); + } + } + } + } +} + + +static void blist_store(struct mwGaimPluginData *pd) { + + struct mwSametimeList *stlist; + struct mwServiceStorage *srvc; + struct mwStorageUnit *unit; + + GaimConnection *gc; + + struct mwPutBuffer *b; + struct mwOpaque *o; + + g_return_if_fail(pd != NULL); + + srvc = pd->srvc_store; + g_return_if_fail(srvc != NULL); + + gc = pd->gc; + + /* check if we should do this, according to user prefs */ + if(! BLIST_CHOICE_IS_SAVE()) { + DEBUG_INFO("preferences indicate not to save remote blist\n"); + return; + + } else if(MW_SERVICE_IS_DEAD(srvc)) { + DEBUG_INFO("aborting save of blist: storage service is not alive\n"); + return; + + } else { + DEBUG_INFO("saving remote blist\n"); + } + + /* create and export to a list object */ + stlist = mwSametimeList_new(); + blist_export(gc, stlist); + + /* write it to a buffer */ + b = mwPutBuffer_new(); + mwSametimeList_put(b, stlist); + mwSametimeList_free(stlist); + + /* put the buffer contents into a storage unit */ + unit = mwStorageUnit_new(mwStore_AWARE_LIST); + o = mwStorageUnit_asOpaque(unit); + mwPutBuffer_finalize(o, b); + + /* save the storage unit to the service */ + mwServiceStorage_save(srvc, unit, NULL, NULL, NULL); +} + + +static gboolean blist_save_cb(gpointer data) { + struct mwGaimPluginData *pd = data; + + blist_store(pd); + pd->save_event = 0; + return FALSE; +} + + +/** schedules the buddy list to be saved to the server */ +static void blist_schedule(struct mwGaimPluginData *pd) { + if(pd->save_event) return; + + pd->save_event = gaim_timeout_add(BLIST_SAVE_SECONDS * 1000, + blist_save_cb, pd); +} + + +/** Actually add a buddy to the aware service, and schedule the buddy + list to be saved to the server */ +static void buddy_add(struct mwGaimPluginData *pd, + GaimBuddy *buddy) { + + struct mwAwareIdBlock idb = { mwAware_USER, (char *) buddy->name, NULL }; + struct mwAwareList *list; + + GaimGroup *group; + GList *add; + + add = g_list_prepend(NULL, &idb); + + group = gaim_find_buddys_group(buddy); + list = list_ensure(pd, group); + + if(mwAwareList_addAware(list, add)) { + gaim_blist_remove_buddy(buddy); + } + + blist_schedule(pd); + + g_list_free(add); +} + + +/** ensure that a GaimBuddy exists in the group with data + appropriately matching the st user entry from the st list */ +static GaimBuddy *buddy_ensure(GaimConnection *gc, GaimGroup *group, + struct mwSametimeUser *stuser) { + + struct mwGaimPluginData *pd = gc->proto_data; + GaimBuddy *buddy; + GaimAccount *acct = gaim_connection_get_account(gc); + + const char *id = mwSametimeUser_getUser(stuser); + const char *name = mwSametimeUser_getShortName(stuser); + const char *alias = mwSametimeUser_getAlias(stuser); + enum mwSametimeUserType type = mwSametimeUser_getType(stuser); + + g_return_val_if_fail(id != NULL, NULL); + g_return_val_if_fail(strlen(id) > 0, NULL); + + buddy = gaim_find_buddy_in_group(acct, id, group); + if(! buddy) { + buddy = gaim_buddy_new(acct, id, alias); + + gaim_blist_add_buddy(buddy, NULL, group, NULL); + buddy_add(pd, buddy); + } + + gaim_blist_alias_buddy(buddy, alias); + gaim_blist_server_alias_buddy(buddy, name); + gaim_blist_node_set_string((GaimBlistNode *) buddy, BUDDY_KEY_NAME, name); + gaim_blist_node_set_int((GaimBlistNode *) buddy, BUDDY_KEY_TYPE, type); + + return buddy; +} + + +static void group_add(struct mwGaimPluginData *pd, + GaimGroup *group) { + + struct mwAwareIdBlock idb = { mwAware_GROUP, NULL, NULL }; + struct mwAwareList *list; + const char *n; + GList *add; + + n = gaim_blist_node_get_string((GaimBlistNode *) group, GROUP_KEY_NAME); + if(! n) n = group->name; + + idb.user = (char *) n; + add = g_list_prepend(NULL, &idb); + + list = list_ensure(pd, group); + mwAwareList_addAware(list, add); + g_list_free(add); +} + + +/** ensure that a GaimGroup exists in the blist with data + appropriately matching the st group entry from the st list */ +static GaimGroup *group_ensure(GaimConnection *gc, + struct mwSametimeGroup *stgroup) { + GaimAccount *acct; + GaimGroup *group; + GaimBlistNode *gn; + const char *name = mwSametimeGroup_getName(stgroup); + const char *alias = mwSametimeGroup_getAlias(stgroup); + const char *owner; + enum mwSametimeGroupType type = mwSametimeGroup_getType(stgroup); + + acct = gaim_connection_get_account(gc); + owner = gaim_account_get_username(acct); + + group = gaim_find_group(alias); + if(! group) { + group = gaim_group_new(alias); + gaim_blist_add_group(group, NULL); + } + + gn = (GaimBlistNode *) group; + + gaim_blist_node_set_string(gn, GROUP_KEY_NAME, name); + gaim_blist_node_set_int(gn, GROUP_KEY_TYPE, type); + + if(type == mwSametimeGroup_DYNAMIC) { + gaim_blist_node_set_string(gn, GROUP_KEY_OWNER, owner); + group_add(gc->proto_data, group); + } + + return group; +} + + +/** merge the entries from a st list into the gaim blist */ +static void blist_import(GaimConnection *gc, struct mwSametimeList *stlist) { + struct mwSametimeGroup *stgroup; + struct mwSametimeUser *stuser; + + GaimGroup *group; + GaimBuddy *buddy; + + GList *gl, *gtl, *ul, *utl; + + gl = gtl = mwSametimeList_getGroups(stlist); + for(; gl; gl = gl->next) { + + stgroup = (struct mwSametimeGroup *) gl->data; + group = group_ensure(gc, stgroup); + + ul = utl = mwSametimeGroup_getUsers(stgroup); + for(; ul; ul = ul->next) { + + stuser = (struct mwSametimeUser *) ul->data; + buddy = buddy_ensure(gc, group, stuser); + } + g_list_free(utl); + } + g_list_free(gtl); +} + + +/** callback passed to the storage service when it's told to load the + st list */ +static void fetch_blist_cb(struct mwServiceStorage *srvc, + guint32 result, struct mwStorageUnit *item, + gpointer data) { + + struct mwGaimPluginData *pd = data; + struct mwSametimeList *stlist; + struct mwSession *s; + + struct mwGetBuffer *b; + + g_return_if_fail(result == ERR_SUCCESS); + + /* check our preferences for loading */ + if(BLIST_CHOICE_IS_NONE()) { + DEBUG_INFO("preferences indicate not to load remote buddy list\n"); + return; + } + + b = mwGetBuffer_wrap(mwStorageUnit_asOpaque(item)); + + stlist = mwSametimeList_new(); + mwSametimeList_get(b, stlist); + + s = mwService_getSession(MW_SERVICE(srvc)); + blist_import(pd->gc, stlist); + + mwSametimeList_free(stlist); +} + + +/** callback passed to the storage service when it's told to load one + of the default status messages */ +static void fetch_msg_cb(struct mwServiceStorage *srvc, + guint32 result, struct mwStorageUnit *item, + gpointer data) { + + struct mwGaimPluginData *pd = data; + GaimConnection *gc; + GaimAccount *acct; + struct mwSession *session; + char *msg, *m; + + g_return_if_fail(result == ERR_SUCCESS); + + g_return_if_fail(pd != NULL); + + gc = pd->gc; + g_return_if_fail(gc != NULL); + + acct = gaim_connection_get_account(gc); + g_return_if_fail(acct != NULL); + + session = pd->session; + g_return_if_fail(session != NULL); + + m = msg = mwStorageUnit_asString(item); + + /* only load the first (non-empty) line of the collection of + status messages */ + if(m && *m) { + while(*m && isspace(*m)) m++; + if(*m) { + char *tail; + + tail = strchr(m, '\r'); + if(tail) *tail = '\0'; + tail = strchr(m, '\n'); + if(tail) *tail = '\0'; + } + } + + switch(mwStorageUnit_getKey(item)) { + case mwStore_AWAY_MESSAGES: + DEBUG_INFO("setting away message to \"%s\"\n", NSTR(m)); + gaim_account_set_string(acct, MW_KEY_AWAY_MSG, m); + break; + + case mwStore_BUSY_MESSAGES: + DEBUG_INFO("setting busy message to \"%s\"\n", NSTR(m)); + gaim_account_set_string(acct, MW_KEY_BUSY_MSG, m); + break; + + case mwStore_ACTIVE_MESSAGES: + DEBUG_INFO("setting active message to \"%s\"\n", NSTR(m)); + gaim_account_set_string(acct, MW_KEY_ACTIVE_MSG, m); + break; + + default: + g_free(msg); + g_return_if_reached(); + } + + g_free(msg); + msg = NULL; + +#if 0 + /* XXX */ + if(!gc->away_state || !strcmp(gc->away_state, MW_STATE_ACTIVE)) { + msg = MW_STATE_ACTIVE; + } else if(gc->away_state && !strcmp(gc->away_state, MW_STATE_AWAY)) { + msg = MW_STATE_AWAY; + } else if(gc->away_state && !strcmp(gc->away_state, MW_STATE_BUSY)) { + msg = MW_STATE_BUSY; + } + + if(msg) + serv_set_away(gc, msg, NULL); +#endif +} + + +/** signal triggered when a conversation is opened in Gaim */ +static void conversation_created_cb(GaimConversation *g_conv, + struct mwGaimPluginData *pd) { + + /* we need to tell the IM service to negotiate features for the + conversation right away, otherwise it'll wait until the first + message is sent before offering NotesBuddy features. Therefore + whenever Gaim creates a conversation, we'll immediately open the + channel to the other side and figure out what the target can + handle. Unfortunately, this makes us vulnerable to Psychic Mode, + whereas a more lazy negotiation based on the first message + isn't */ + + GaimConnection *gc; + struct mwIdBlock who = { 0, 0 }; + struct mwConversation *conv; + + gc = gaim_conversation_get_gc(g_conv); + if(pd->gc != gc) + return; /* not ours */ + + if(gaim_conversation_get_type(g_conv) != GAIM_CONV_IM) + return; /* wrong type */ + + who.user = (char *) gaim_conversation_get_name(g_conv); + conv = mwServiceIm_getConversation(pd->srvc_im, &who); + + convo_features(conv); + + if(mwConversation_isClosed(conv)) + mwConversation_open(conv); +} + + +static void blist_menu_nab(GaimBlistNode *node, gpointer data) { + struct mwGaimPluginData *pd = data; + GaimConnection *gc; + + GaimGroup *group = (GaimGroup *) node; + + GString *str; + char *tmp; + + g_return_if_fail(pd != NULL); + + gc = pd->gc; + g_return_if_fail(gc != NULL); + + g_return_if_fail(GAIM_BLIST_NODE_IS_GROUP(node)); + + str = g_string_new(NULL); + + tmp = (char *) gaim_blist_node_get_string(node, GROUP_KEY_NAME); + + g_string_append_printf(str, "Group Title: %s
", group->name); + g_string_append_printf(str, "Notes Group ID: %s
", tmp); + + tmp = g_strdup_printf("Info for Group %s", group->name); + + gaim_notify_formatted(gc, tmp, "Notes Address Book Information", + NULL, str->str, NULL, NULL); + + g_free(tmp); + g_string_free(str, TRUE); +} + + +/** The normal blist menu prpl function doesn't get called for groups, + so we use the blist-node-extended-menu signal to trigger this + handler */ +static void blist_node_menu_cb(GaimBlistNode *node, + GList **menu, struct mwGaimPluginData *pd) { + GaimBlistNodeAction *act; + + if(GAIM_BLIST_NODE_IS_GROUP(node)) { + const char *owner; + GaimAccount *acct; + + owner = gaim_blist_node_get_string(node, GROUP_KEY_OWNER); + if(! owner) return; + + acct = gaim_accounts_find(owner, PLUGIN_ID); + if(! acct) return; + if(! gaim_account_is_connected(acct)) return; + if(acct != gaim_connection_get_account(pd->gc)) return; + + act = gaim_blist_node_action_new("Get Notes Address Book Info", + blist_menu_nab, pd, NULL); + + *menu = g_list_append(*menu, NULL); + *menu = g_list_append(*menu, act); + } +} + + +/** Last thing to happen from a started session */ +static void services_starting(struct mwGaimPluginData *pd) { + + GaimConnection *gc; + GaimAccount *acct; + struct mwStorageUnit *unit; + GaimBuddyList *blist; + GaimBlistNode *l; + + gc = pd->gc; + acct = gaim_connection_get_account(gc); + + /* grab the buddy list from the server */ + unit = mwStorageUnit_new(mwStore_AWARE_LIST); + mwServiceStorage_load(pd->srvc_store, unit, fetch_blist_cb, pd, NULL); + + /* fetch the away/busy/active messages from the server */ + unit = mwStorageUnit_new(mwStore_AWAY_MESSAGES); + mwServiceStorage_load(pd->srvc_store, unit, fetch_msg_cb, pd, NULL); + + unit = mwStorageUnit_new(mwStore_BUSY_MESSAGES); + mwServiceStorage_load(pd->srvc_store, unit, fetch_msg_cb, pd, NULL); + + unit = mwStorageUnit_new(mwStore_ACTIVE_MESSAGES); + mwServiceStorage_load(pd->srvc_store, unit, fetch_msg_cb, pd, NULL); + + /* start watching for new conversations */ + gaim_signal_connect(gaim_conversations_get_handle(), + "conversation-created", gc, + GAIM_CALLBACK(conversation_created_cb), pd); + + /* watch for group extended menu items */ + gaim_signal_connect(gaim_blist_get_handle(), + "blist-node-extended-menu", gc, + GAIM_CALLBACK(blist_node_menu_cb), pd); + + /* find all the NAB groups and subscribe to them */ + blist = gaim_get_blist(); + for(l = blist->root; l; l = l->next) { + GaimGroup *group = (GaimGroup *) l; + enum mwSametimeGroupType gt; + const char *owner; + + if(! GAIM_BLIST_NODE_IS_GROUP(l)) continue; + + /* if the group is ownerless, or has an owner and we're not it, + skip it */ + owner = gaim_blist_node_get_string(l, GROUP_KEY_OWNER); + if(!owner || strcmp(owner, gaim_account_get_username(acct))) + continue; + + gt = gaim_blist_node_get_int(l, GROUP_KEY_TYPE); + if(gt == mwSametimeGroup_DYNAMIC) + group_add(pd, group); + } + + /* set the aware attributes */ + /* indicate we understand what AV prefs are, but don't support any */ + mwServiceAware_setAttributeBoolean(pd->srvc_aware, + mwAttribute_AV_PREFS_SET, TRUE); + mwServiceAware_unsetAttribute(pd->srvc_aware, mwAttribute_MICROPHONE); + mwServiceAware_unsetAttribute(pd->srvc_aware, mwAttribute_SPEAKERS); + mwServiceAware_unsetAttribute(pd->srvc_aware, mwAttribute_VIDEO_CAMERA); + + /* ... but we can do file transfers! */ + mwServiceAware_setAttributeBoolean(pd->srvc_aware, + mwAttribute_FILE_TRANSFER, TRUE); +} + + +/** called from mw_session_stateChange when the session's state is + mwSession_STARTED. Any finalizing of start-up stuff should go + here */ +static void session_started(struct mwGaimPluginData *pd) { + + /* XXX setup status */ + + /* use our services to do neat things */ + services_starting(pd); +} + + +static void mw_session_stateChange(struct mwSession *session, + enum mwSessionState state, guint32 info) { + struct mwGaimPluginData *pd; + GaimConnection *gc; + char *msg = NULL; + + pd = mwSession_getClientData(session); + gc = pd->gc; + + switch(state) { + case mwSession_STARTING: + msg = _("Sending Handshake"); + gaim_connection_update_progress(gc, msg, 2, MW_CONNECT_STEPS); + break; + + case mwSession_HANDSHAKE: + msg = _("Waiting for Handshake Acknowledgement"); + gaim_connection_update_progress(gc, msg, 3, MW_CONNECT_STEPS); + break; + + case mwSession_HANDSHAKE_ACK: + msg = _("Handshake Acknowledged, Sending Login"); + gaim_connection_update_progress(gc, msg, 4, MW_CONNECT_STEPS); + break; + + case mwSession_LOGIN: + msg = _("Waiting for Login Acknowledgement"); + gaim_connection_update_progress(gc, msg, 5, MW_CONNECT_STEPS); + break; + + case mwSession_LOGIN_REDIR: + msg = _("Login Redirected"); + gaim_connection_update_progress(gc, msg, 6, MW_CONNECT_STEPS); + break; + + case mwSession_LOGIN_ACK: + msg = _("Login Acknowledged"); + gaim_connection_update_progress(gc, msg, 7, MW_CONNECT_STEPS); + break; + + case mwSession_STARTED: + msg = _("Connected to Sametime Community Server"); + gaim_connection_update_progress(gc, msg, 8, MW_CONNECT_STEPS); + gaim_connection_set_state(gc, GAIM_CONNECTED); + /* XXX serv_finish_login(gc); */ + + session_started(pd); + break; + + case mwSession_STOPPING: + if(info & ERR_FAILURE) { + msg = mwError(info); + gaim_connection_error(gc, msg); + g_free(msg); + } + break; + + case mwSession_STOPPED: + break; + + case mwSession_UNKNOWN: + default: + DEBUG_WARN("session in unknown state\n"); + } +} + + +static void mw_session_setPrivacyInfo(struct mwSession *session) { + /** @todo implement privacy one of these days */ + + struct mwGaimPluginData *pd; + GaimConnection *gc; + GaimAccount *acct; + struct mwPrivacyInfo *privacy; + GSList *l, **ll; + guint count; + + DEBUG_INFO("privacy information set from server\n"); + + g_return_if_fail(session != NULL); + + pd = mwSession_getClientData(session); + g_return_if_fail(pd != NULL); + + gc = pd->gc; + g_return_if_fail(gc != NULL); + + acct = gaim_connection_get_account(gc); + g_return_if_fail(acct != NULL); + + privacy = mwSession_getPrivacyInfo(session); + count = privacy->count; + + ll = (privacy->deny)? &acct->deny: &acct->permit; + for(l = *ll; l; l = l->next) g_free(l->data); + g_slist_free(*ll); + l = *ll = NULL; + + while(count--) { + struct mwUserItem *u = privacy->users + count; + l = g_slist_prepend(l, g_strdup(u->id)); + } + *ll = l; +} + + +static void mw_session_setUserStatus(struct mwSession *session) { + struct mwGaimPluginData *pd; + GaimConnection *gc; + struct mwAwareIdBlock idb = { mwAware_USER, NULL, NULL }; + struct mwUserStatus *stat; + + g_return_if_fail(session != NULL); + + pd = mwSession_getClientData(session); + g_return_if_fail(pd != NULL); + + gc = pd->gc; + g_return_if_fail(gc != NULL); + + idb.user = mwSession_getProperty(session, mwSession_AUTH_USER_ID); + stat = mwSession_getUserStatus(session); + + /* trigger an update of our own status if we're in the buddy list */ + mwServiceAware_setStatus(pd->srvc_aware, &idb, stat); +} + + +static void mw_session_admin(struct mwSession *session, + const char *text) { + + GaimConnection *gc = session_to_gc(session); + g_return_if_fail(gc != NULL); + + /** @todo Admin alerts should probably be in a conversation window + rather than a gaim_notify_message. Or in some sort of updating + dialog, or something. */ + + gaim_notify_message(gc, GAIM_NOTIFY_MSG_INFO, _("Admin Alert"), + text, NULL, NULL, NULL); +} + + +/** called from read_cb, attempts to read available data from sock and + pass it to the session, passing back the return code from the read + call for handling in read_cb */ +static int read_recv(struct mwSession *session, int sock) { + char buf[BUF_LEN]; + int len; + + len = read(sock, buf, BUF_LEN); + if(len > 0) mwSession_recv(session, buf, len); + + return len; +} + + +/** callback triggered from gaim_input_add, watches the socked for + available data to be processed by the session */ +static void read_cb(gpointer data, gint source, + GaimInputCondition cond) { + + struct mwGaimPluginData *pd = data; + int ret = 0, err = 0; + + g_return_if_fail(pd != NULL); + + if(cond & GAIM_INPUT_READ) { + ret = read_recv(pd->session, pd->socket); + } + + /* normal operation ends here */ + if(ret > 0) return; + + err = errno; + + /* read problem occured if we're here, so we'll need to take care of + it and clean up internal state */ + + if(pd->socket) { + close(pd->socket); + pd->socket = 0; + } + + if(pd->gc->inpa) { + gaim_input_remove(pd->gc->inpa); + pd->gc->inpa = 0; + } + + if(! ret) { + DEBUG_INFO("connection reset\n"); + gaim_connection_error(pd->gc, "Connection reset"); + + } else if(ret < 0) { + char *msg = strerror(err); + DEBUG_INFO("error in read callback: %s\n", msg); + + msg = g_strdup_printf("Error reading from socket: %s", msg); + gaim_connection_error(pd->gc, msg); + g_free(msg); + } +} + + +/** Callback passed to gaim_proxy_connect when an account is logged + in, and if the session logging in receives a redirect message */ +static void connect_cb(gpointer data, gint source, + GaimInputCondition cond) { + + struct mwGaimPluginData *pd = data; + GaimConnection *gc = pd->gc; + + if(! g_list_find(gaim_connections_get_all(), pd->gc)) { + close(source); + g_return_if_reached(); + } + + if(source < 0) { + /* connection failed */ + + if(pd->socket) { + /* this is a redirect connect, force login on existing socket */ + mwSession_forceLogin(pd->session); + + } else { + /* this is a regular connect, error out */ + gaim_connection_error(pd->gc, "Unable to connect to host"); + } + + return; + } + + if(pd->socket) { + /* stop any existing login attempt */ + mwSession_stop(pd->session, ERR_SUCCESS); + } + + pd->socket = source; + gc->inpa = gaim_input_add(source, GAIM_INPUT_READ, read_cb, pd); + + mwSession_start(pd->session); +} + + +static void mw_session_loginRedirect(struct mwSession *session, + const char *host) { + + struct mwGaimPluginData *pd; + GaimConnection *gc; + GaimAccount *account; + guint port; + + pd = mwSession_getClientData(session); + gc = pd->gc; + account = gaim_connection_get_account(gc); + port = gaim_account_get_int(account, "port", MW_PLUGIN_DEFAULT_PORT); + + if(gaim_prefs_get_bool(MW_PRPL_OPT_FORCE_LOGIN) || + gaim_proxy_connect(account, host, port, connect_cb, pd)) { + + mwSession_forceLogin(session); + } +} + + +static struct mwSessionHandler mw_session_handler = { + .io_write = mw_session_io_write, + .io_close = mw_session_io_close, + .clear = mw_session_clear, + .on_stateChange = mw_session_stateChange, + .on_setPrivacyInfo = mw_session_setPrivacyInfo, + .on_setUserStatus = mw_session_setUserStatus, + .on_admin = mw_session_admin, + .on_loginRedirect = mw_session_loginRedirect, +}; + + +static void mw_aware_on_attrib(struct mwServiceAware *srvc, + struct mwAwareAttribute *attrib) { + + ; /** @todo handle server attributes. There may be some stuff we + actually want to look for, but I'm not aware of anything right + now.*/ +} + + +static void mw_aware_clear(struct mwServiceAware *srvc) { + ; /* nothing for now */ +} + + +static struct mwAwareHandler mw_aware_handler = { + .on_attrib = mw_aware_on_attrib, + .clear = mw_aware_clear, +}; + + +static struct mwServiceAware *mw_srvc_aware_new(struct mwSession *s) { + struct mwServiceAware *srvc; + srvc = mwServiceAware_new(s, &mw_aware_handler); + return srvc; +}; + + +static void mw_conf_invited(struct mwConference *conf, + struct mwLoginInfo *inviter, + const char *invitation) { + + struct mwServiceConference *srvc; + struct mwSession *session; + struct mwGaimPluginData *pd; + GaimConnection *gc; + + char *c_inviter, *c_name, *c_topic, *c_invitation; + GHashTable *ht; + + srvc = mwConference_getService(conf); + session = mwService_getSession(MW_SERVICE(srvc)); + pd = mwSession_getClientData(session); + gc = pd->gc; + + ht = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free); + + c_inviter = g_strdup(inviter->user_id); + g_hash_table_insert(ht, CHAT_KEY_CREATOR, c_inviter); + + c_name = g_strdup(mwConference_getName(conf)); + g_hash_table_insert(ht, CHAT_KEY_NAME, c_name); + + c_topic = g_strdup(mwConference_getTitle(conf)); + g_hash_table_insert(ht, CHAT_KEY_TOPIC, c_topic); + + c_invitation = g_strdup(invitation); + g_hash_table_insert(ht, CHAT_KEY_INVITE, c_invitation); + + DEBUG_INFO("received invitation from '%s' to join ('%s','%s'): '%s'\n", + NSTR(c_inviter), NSTR(c_name), + NSTR(c_topic), NSTR(c_invitation)); + + serv_got_chat_invite(gc, c_topic, c_inviter, c_invitation, ht); +} + + +/* The following mess helps us relate a mwConference to a GaimConvChat + in the various forms by which either may be indicated */ + +#define CONF_TO_ID(conf) (GPOINTER_TO_INT(conf)) +#define ID_TO_CONF(pd, id) (conf_find_by_id((pd), (id))) + +#define CHAT_TO_ID(chat) (gaim_conv_chat_get_id(chat)) +#define ID_TO_CHAT(id) (gaim_find_chat(id)) + +#define CHAT_TO_CONF(pd, chat) \ + (ID_TO_CONF((pd), CHAT_TO_ID(chat))) + +#define CONF_TO_CHAT(conf) \ + (ID_TO_CHAT(CONF_TO_ID(conf))) + + +static struct mwConference * +conf_find_by_id(struct mwGaimPluginData *pd, int id) { + + struct mwServiceConference *srvc = pd->srvc_conf; + struct mwConference *conf = NULL; + GList *l, *ll; + + ll = mwServiceConference_getConferences(srvc); + for(l = ll; l; l = l->next) { + struct mwConference *c = l->data; + GaimConvChat *h = mwConference_getClientData(c); + + if(CHAT_TO_ID(h) == id) { + conf = c; + break; + } + } + g_list_free(ll); + + return conf; +} + + +static void mw_conf_opened(struct mwConference *conf, GList *members) { + struct mwServiceConference *srvc; + struct mwSession *session; + struct mwGaimPluginData *pd; + GaimConnection *gc; + GaimConversation *g_conf; + + const char *n = mwConference_getName(conf); + + DEBUG_INFO("conf %s opened, %u initial members\n", + NSTR(n), g_list_length(members)); + + srvc = mwConference_getService(conf); + session = mwService_getSession(MW_SERVICE(srvc)); + pd = mwSession_getClientData(session); + gc = pd->gc; + + g_conf = serv_got_joined_chat(gc, CONF_TO_ID(conf), + mwConference_getTitle(conf)); + + mwConference_setClientData(conf, GAIM_CONV_CHAT(g_conf), NULL); + + for(; members; members = members->next) { + struct mwLoginInfo *peer = members->data; + gaim_conv_chat_add_user(GAIM_CONV_CHAT(g_conf), peer->user_id, + NULL, GAIM_CBFLAGS_NONE, FALSE); + } +} + + +static void mw_conf_closed(struct mwConference *conf, guint32 reason) { + struct mwServiceConference *srvc; + struct mwSession *session; + struct mwGaimPluginData *pd; + GaimConnection *gc; + + const char *n = mwConference_getName(conf); + char *msg = mwError(reason); + + DEBUG_INFO("conf %s closed, 0x%08x\n", NSTR(n), reason); + + srvc = mwConference_getService(conf); + session = mwService_getSession(MW_SERVICE(srvc)); + pd = mwSession_getClientData(session); + gc = pd->gc; + + serv_got_chat_left(gc, CONF_TO_ID(conf)); + + gaim_notify_error(gc, "Conference Closed", NULL, msg); + g_free(msg); +} + + +static void mw_conf_peer_joined(struct mwConference *conf, + struct mwLoginInfo *peer) { + + struct mwServiceConference *srvc; + struct mwSession *session; + struct mwGaimPluginData *pd; + GaimConnection *gc; + GaimConvChat *g_conf; + + const char *n = mwConference_getName(conf); + + DEBUG_INFO("%s joined conf %s\n", NSTR(peer->user_id), NSTR(n)); + + srvc = mwConference_getService(conf); + session = mwService_getSession(MW_SERVICE(srvc)); + pd = mwSession_getClientData(session); + gc = pd->gc; + + g_conf = mwConference_getClientData(conf); + g_return_if_fail(g_conf != NULL); + + gaim_conv_chat_add_user(g_conf, peer->user_id, + NULL, GAIM_CBFLAGS_NONE, TRUE); +} + + +static void mw_conf_peer_parted(struct mwConference *conf, + struct mwLoginInfo *peer) { + + struct mwServiceConference *srvc; + struct mwSession *session; + struct mwGaimPluginData *pd; + GaimConnection *gc; + GaimConvChat *g_conf; + + const char *n = mwConference_getName(conf); + + DEBUG_INFO("%s left conf %s\n", NSTR(peer->user_id), NSTR(n)); + + srvc = mwConference_getService(conf); + session = mwService_getSession(MW_SERVICE(srvc)); + pd = mwSession_getClientData(session); + gc = pd->gc; + + g_conf = mwConference_getClientData(conf); + g_return_if_fail(g_conf != NULL); + + gaim_conv_chat_remove_user(g_conf, peer->user_id, NULL); +} + + +static void mw_conf_text(struct mwConference *conf, + struct mwLoginInfo *who, const char *text) { + + struct mwServiceConference *srvc; + struct mwSession *session; + struct mwGaimPluginData *pd; + GaimConnection *gc; + char *esc; + + srvc = mwConference_getService(conf); + session = mwService_getSession(MW_SERVICE(srvc)); + pd = mwSession_getClientData(session); + gc = pd->gc; + + esc = g_markup_escape_text(text, -1); + serv_got_chat_in(gc, CONF_TO_ID(conf), who->user_id, 0, esc, time(NULL)); + g_free(esc); +} + + +static void mw_conf_typing(struct mwConference *conf, + struct mwLoginInfo *who, gboolean typing) { + + /* gaim really has no good way to expose this to the user. */ + + const char *n = mwConference_getName(conf); + const char *w = who->user_id; + + if(typing) { + DEBUG_INFO("%s in conf %s: \n", NSTR(w), NSTR(n)); + + } else { + DEBUG_INFO("%s in conf %s: \n", NSTR(w), NSTR(n)); + } +} + + +static void mw_conf_clear(struct mwServiceConference *srvc) { + ; +} + + +static struct mwConferenceHandler mw_conference_handler = { + .on_invited = mw_conf_invited, + .conf_opened = mw_conf_opened, + .conf_closed = mw_conf_closed, + .on_peer_joined = mw_conf_peer_joined, + .on_peer_parted = mw_conf_peer_parted, + .on_text = mw_conf_text, + .on_typing = mw_conf_typing, + .clear = mw_conf_clear, +}; + + +static struct mwServiceConference *mw_srvc_conf_new(struct mwSession *s) { + struct mwServiceConference *srvc; + srvc = mwServiceConference_new(s, &mw_conference_handler); + return srvc; +} + + +static void ft_incoming_cancel(GaimXfer *xfer) { + /* incoming transfer rejected or canceled in-progress */ + struct mwFileTransfer *ft = xfer->data; + if(ft) mwFileTransfer_reject(ft); +} + + +static void ft_incoming_init(GaimXfer *xfer) { + /* incoming transfer accepted */ + + /* - accept the mwFileTransfer + - open/create the local FILE "wb" + - stick the FILE's fp in xfer->dest_fp + */ + + struct mwFileTransfer *ft; + FILE *fp; + + ft = xfer->data; + + fp = g_fopen(xfer->local_filename, "wb"); + if(! fp) { + mwFileTransfer_cancel(ft); + return; + } + + xfer->dest_fp = fp; + mwFileTransfer_accept(ft); +} + + +static void mw_ft_offered(struct mwFileTransfer *ft) { + /* + - create a gaim ft object + - offer it + */ + + struct mwServiceFileTransfer *srvc; + struct mwSession *session; + struct mwGaimPluginData *pd; + GaimConnection *gc; + GaimAccount *acct; + const char *who; + GaimXfer *xfer; + + /* @todo add some safety checks */ + srvc = mwFileTransfer_getService(ft); + session = mwService_getSession(MW_SERVICE(srvc)); + pd = mwSession_getClientData(session); + gc = pd->gc; + acct = gaim_connection_get_account(gc); + + who = mwFileTransfer_getUser(ft)->user; + + DEBUG_INFO("file transfer %p offered\n", ft); + DEBUG_INFO(" from: %s\n", NSTR(who)); + DEBUG_INFO(" file: %s\n", NSTR(mwFileTransfer_getFileName(ft))); + DEBUG_INFO(" size: %u\n", mwFileTransfer_getFileSize(ft)); + DEBUG_INFO(" text: %s\n", NSTR(mwFileTransfer_getMessage(ft))); + + xfer = gaim_xfer_new(acct, GAIM_XFER_RECEIVE, who); + + gaim_xfer_ref(xfer); + mwFileTransfer_setClientData(ft, xfer, (GDestroyNotify) gaim_xfer_unref); + xfer->data = ft; + + gaim_xfer_set_init_fnc(xfer, ft_incoming_init); + gaim_xfer_set_cancel_recv_fnc(xfer, ft_incoming_cancel); + gaim_xfer_set_request_denied_fnc(xfer, ft_incoming_cancel); + + gaim_xfer_set_filename(xfer, mwFileTransfer_getFileName(ft)); + gaim_xfer_set_size(xfer, mwFileTransfer_getFileSize(ft)); + gaim_xfer_set_message(xfer, mwFileTransfer_getMessage(ft)); + + gaim_xfer_request(xfer); +} + + +static void ft_send(struct mwFileTransfer *ft, FILE *fp) { + char buf[BUF_LONG]; + struct mwOpaque o = { .data = buf, .len = BUF_LONG }; + guint32 rem; + GaimXfer *xfer; + + xfer = mwFileTransfer_getClientData(ft); + + rem = mwFileTransfer_getRemaining(ft); + if(rem < BUF_LONG) o.len = rem; + + if(fread(buf, (size_t) o.len, 1, fp)) { + + /* calculate progress first. update is displayed upon ack */ + xfer->bytes_sent += o.len; + xfer->bytes_remaining -= o.len; + + /* ... send data second */ + mwFileTransfer_send(ft, &o); + + } else { + int err = errno; + DEBUG_WARN("problem reading from file %s: %s", + NSTR(mwFileTransfer_getFileName(ft)), strerror(err)); + + mwFileTransfer_cancel(ft); + } +} + + +static gboolean ft_idle_cb(struct mwFileTransfer *ft) { + GaimXfer *xfer = mwFileTransfer_getClientData(ft); + g_return_val_if_fail(xfer != NULL, FALSE); + + xfer->watcher = 0; + ft_send(ft, xfer->dest_fp); + + return FALSE; +} + + +static void mw_ft_opened(struct mwFileTransfer *ft) { + /* + - get gaim ft from client data in ft + - set the state to active + */ + + GaimXfer *xfer; + + xfer = mwFileTransfer_getClientData(ft); + + if(! xfer) { + mwFileTransfer_cancel(ft); + mwFileTransfer_free(ft); + g_return_if_reached(); + } + + gaim_xfer_update_progress(xfer); + + if(gaim_xfer_get_type(xfer) == GAIM_XFER_SEND) { + xfer->watcher = g_idle_add((GSourceFunc)ft_idle_cb, ft); + xfer->dest_fp = g_fopen(xfer->local_filename, "rb"); + } +} + + +static void mw_ft_closed(struct mwFileTransfer *ft, guint32 code) { + /* + - get gaim ft from client data in ft + - indicate rejection/cancelation/completion + - free the file transfer itself + */ + + GaimXfer *xfer; + + xfer = mwFileTransfer_getClientData(ft); + if(xfer) { + xfer->data = NULL; + + if(mwFileTransfer_isDone(ft)) { + gaim_xfer_set_completed(xfer, TRUE); + gaim_xfer_end(xfer); + + } else if(mwFileTransfer_isCancelLocal(ft)) { + /* calling gaim_xfer_cancel_local is redundant, since that's + probably what triggered this function to be called */ + ; + + } else if(mwFileTransfer_isCancelRemote(ft)) { + /* steal the reference for the xfer */ + mwFileTransfer_setClientData(ft, NULL, NULL); + gaim_xfer_cancel_remote(xfer); + + /* drop the stolen reference */ + gaim_xfer_unref(xfer); + return; + } + } + + mwFileTransfer_free(ft); +} + + +static void mw_ft_recv(struct mwFileTransfer *ft, + struct mwOpaque *data) { + /* + - get gaim ft from client data in ft + - update transfered percentage + - if done, destroy the ft, disassociate from gaim ft + */ + + GaimXfer *xfer; + FILE *fp; + + xfer = mwFileTransfer_getClientData(ft); + g_return_if_fail(xfer != NULL); + + fp = xfer->dest_fp; + g_return_if_fail(fp != NULL); + + /* we must collect and save our precious data */ + fwrite(data->data, 1, data->len, fp); + + /* update the progress */ + xfer->bytes_sent += data->len; + xfer->bytes_remaining -= data->len; + gaim_xfer_update_progress(xfer); + + /* let the other side know we got it, and to send some more */ + mwFileTransfer_ack(ft); +} + + +static void mw_ft_ack(struct mwFileTransfer *ft) { + GaimXfer *xfer; + + xfer = mwFileTransfer_getClientData(ft); + g_return_if_fail(xfer != NULL); + g_return_if_fail(xfer->watcher == 0); + + gaim_xfer_update_progress(xfer); + + if(mwFileTransfer_isOpen(ft)) + xfer->watcher = g_idle_add((GSourceFunc)ft_idle_cb, ft); +} + + +static void mw_ft_clear(struct mwServiceFileTransfer *srvc) { + ; +} + + +static struct mwFileTransferHandler mw_ft_handler = { + .ft_offered = mw_ft_offered, + .ft_opened = mw_ft_opened, + .ft_closed = mw_ft_closed, + .ft_recv = mw_ft_recv, + .ft_ack = mw_ft_ack, + .clear = mw_ft_clear, +}; + + +static struct mwServiceFileTransfer *mw_srvc_ft_new(struct mwSession *s) { + struct mwServiceFileTransfer *srvc; + GHashTable *ft_map; + + ft_map = g_hash_table_new(g_direct_hash, g_direct_equal); + + srvc = mwServiceFileTransfer_new(s, &mw_ft_handler); + mwService_setClientData(MW_SERVICE(srvc), ft_map, + (GDestroyNotify) g_hash_table_destroy); + + return srvc; +} + + +static void convo_data_free(struct convo_data *cd) { + GList *l; + + /* clean the queue */ + for(l = cd->queue; l; l = g_list_delete_link(l, l)) { + struct convo_msg *m = l->data; + if(m->clear) m->clear(m->data); + g_free(m); + } + + g_free(cd); +} + + +/** allocates a convo_data structure and associates it with the + conversation in the client data slot */ +static void convo_data_new(struct mwConversation *conv) { + struct convo_data *cd; + + g_return_if_fail(conv != NULL); + + if(mwConversation_getClientData(conv)) + return; + + cd = g_new0(struct convo_data, 1); + cd->conv = conv; + + mwConversation_setClientData(conv, cd, (GDestroyNotify) convo_data_free); +} + + +static GaimConversation *convo_get_gconv(struct mwConversation *conv) { + struct mwServiceIm *srvc; + struct mwSession *session; + struct mwGaimPluginData *pd; + GaimConnection *gc; + GaimAccount *acct; + + struct mwIdBlock *idb; + + srvc = mwConversation_getService(conv); + session = mwService_getSession(MW_SERVICE(srvc)); + pd = mwSession_getClientData(session); + gc = pd->gc; + acct = gaim_connection_get_account(gc); + + idb = mwConversation_getTarget(conv); + + return gaim_find_conversation_with_account(GAIM_CONV_IM,idb->user, acct); +} + + +static void convo_queue(struct mwConversation *conv, + enum mwImSendType type, gconstpointer data) { + + struct convo_data *cd; + struct convo_msg *m; + + convo_data_new(conv); + cd = mwConversation_getClientData(conv); + + m = g_new0(struct convo_msg, 1); + m->type = type; + + switch(type) { + case mwImSend_PLAIN: + m->data = g_strdup(data); + m->clear = g_free; + break; + + case mwImSend_TYPING: + default: + m->data = (gpointer) data; + m->clear = NULL; + } + + cd->queue = g_list_append(cd->queue, m); +} + + +/* Does what it takes to get an error displayed for a conversation */ +static void convo_error(struct mwConversation *conv, guint32 err) { + GaimConversation *gconv; + char *tmp, *text; + struct mwIdBlock *idb; + + idb = mwConversation_getTarget(conv); + + tmp = mwError(err); + text = g_strconcat("Unable to send message: ", tmp, NULL); + + gconv = convo_get_gconv(conv); + if(gconv && !gaim_conv_present_error(idb->user, gconv->account, text)) { + + g_free(text); + text = g_strdup_printf("Unable to send message to %s:", + (idb->user)? idb->user: "(unknown)"); + gaim_notify_error(gaim_account_get_connection(gconv->account), + NULL, text, tmp); + } + + g_free(tmp); + g_free(text); +} + + +static void convo_queue_send(struct mwConversation *conv) { + struct convo_data *cd; + GList *l; + + cd = mwConversation_getClientData(conv); + + for(l = cd->queue; l; l = g_list_delete_link(l, l)) { + struct convo_msg *m = l->data; + + mwConversation_send(conv, m->type, m->data); + + if(m->clear) m->clear(m->data); + g_free(m); + } + + cd->queue = NULL; +} + + +/** called when a mw conversation leaves a gaim conversation to + inform the gaim conversation that it's unsafe to offer any *cool* + features. */ +static void convo_nofeatures(struct mwConversation *conv) { + GaimConversation *gconv; + GaimConnection *gc; + + gconv = convo_get_gconv(conv); + if(! gconv) return; + + gc = gaim_conversation_get_gc(gconv); + + /* If the account is disconnecting, then the conversation's closing + will call this, and gc will be NULL */ + if(gc) { + gaim_conversation_set_features(gconv, gc->flags); + } +} + + +/** called when a mw conversation and gaim conversation come together, + to inform the gaim conversation of what features to offer the + user */ +static void convo_features(struct mwConversation *conv) { + GaimConversation *gconv; + GaimConnectionFlags feat; + + gconv = convo_get_gconv(conv); + if(! gconv) return; + + feat = gaim_conversation_get_features(gconv); + + if(mwConversation_isOpen(conv)) { + if(mwConversation_supports(conv, mwImSend_HTML)) { + feat |= GAIM_CONNECTION_HTML; + } else { + feat &= ~GAIM_CONNECTION_HTML; + } + + if(mwConversation_supports(conv, mwImSend_MIME)) { + feat &= ~GAIM_CONNECTION_NO_IMAGES; + } else { + feat |= GAIM_CONNECTION_NO_IMAGES; + } + + DEBUG_INFO("conversation features set to 0x%04x\n", feat); + gaim_conversation_set_features(gconv, feat); + + } else { + convo_nofeatures(conv); + } +} + + +/** triggered from mw_conversation_opened if the appropriate plugin + preference is set. This will open a window for the conversation + before the first message is sent. */ +static void convo_do_psychic(struct mwConversation *conv) { + struct mwServiceIm *srvc; + struct mwSession *session; + struct mwGaimPluginData *pd; + GaimConnection *gc; + GaimAccount *acct; + + struct mwIdBlock *idb; + + GaimConversation *gconv; + GaimConvWindow *win; + + srvc = mwConversation_getService(conv); + session = mwService_getSession(MW_SERVICE(srvc)); + pd = mwSession_getClientData(session); + gc = pd->gc; + acct = gaim_connection_get_account(gc); + + idb = mwConversation_getTarget(conv); + + gconv = gaim_find_conversation_with_account(GAIM_CONV_IM, idb->user, acct); + if(! gconv) { + gconv = gaim_conversation_new(GAIM_CONV_IM, acct, idb->user); + } + + g_return_if_fail(gconv != NULL); + + win = gaim_conversation_get_window(gconv); + g_return_if_fail(win != NULL); + + gaim_conv_window_show(win); +} + + +static void mw_conversation_opened(struct mwConversation *conv) { + struct mwServiceIm *srvc; + struct mwSession *session; + struct mwGaimPluginData *pd; + GaimConnection *gc; + GaimAccount *acct; + + struct convo_dat *cd; + + srvc = mwConversation_getService(conv); + session = mwService_getSession(MW_SERVICE(srvc)); + pd = mwSession_getClientData(session); + gc = pd->gc; + acct = gaim_connection_get_account(gc); + + /* set up the queue */ + cd = mwConversation_getClientData(conv); + if(cd) { + convo_queue_send(conv); + + if(! convo_get_gconv(conv)) { + mwConversation_free(conv); + return; + } + + } else { + convo_data_new(conv); + + if(gaim_prefs_get_bool(MW_PRPL_OPT_PSYCHIC)) { + convo_do_psychic(conv); + } + } + + { /* record the client key for the buddy */ + GaimBuddy *buddy; + struct mwLoginInfo *info; + info = mwConversation_getTargetInfo(conv); + + buddy = gaim_find_buddy(acct, info->user_id); + if(buddy) { + gaim_blist_node_set_int((GaimBlistNode *) buddy, + BUDDY_KEY_CLIENT, info->type); + } + } + + convo_features(conv); +} + + +static void mw_conversation_closed(struct mwConversation *conv, + guint32 reason) { + + struct convo_data *cd; + + g_return_if_fail(conv != NULL); + + cd = mwConversation_getClientData(conv); + if(reason && cd && cd->queue) { + convo_error(conv, reason); + } + +#if 0 + /* don't do this, to prevent the occasional weird sending of + formatted messages as plaintext when the other end closes the + conversation after we've begun composing the message */ + convo_nofeatures(conv); +#endif + + mwConversation_removeClientData(conv); +} + + +static void im_recv_text(struct mwConversation *conv, + struct mwGaimPluginData *pd, + const char *msg) { + + struct mwIdBlock *idb; + char *txt, *esc; + + idb = mwConversation_getTarget(conv); + txt = gaim_utf8_try_convert(msg); + esc = g_markup_escape_text(txt, -1); + + serv_got_im(pd->gc, idb->user, esc, 0, time(NULL)); + + g_free(txt); + g_free(esc); +} + + +static void im_recv_typing(struct mwConversation *conv, + struct mwGaimPluginData *pd, + gboolean typing) { + + struct mwIdBlock *idb; + idb = mwConversation_getTarget(conv); + + serv_got_typing(pd->gc, idb->user, 0, + typing? GAIM_TYPING: GAIM_NOT_TYPING); +} + + +static void im_recv_html(struct mwConversation *conv, + struct mwGaimPluginData *pd, + const char *msg) { + + struct mwIdBlock *idb; + char *txt; + + idb = mwConversation_getTarget(conv); + txt = gaim_utf8_try_convert(msg); + + serv_got_im(pd->gc, idb->user, txt, 0, time(NULL)); + + g_free(txt); +} + + +static void im_recv_subj(struct mwConversation *conv, + struct mwGaimPluginData *pd, + const char *subj) { + + /** @todo somehow indicate receipt of a conversation subject. It + would also be nice if we added a /topic command for the + protocol */ + ; +} + + +/** generate "cid:908@20582notesbuddy" from "<908@20582notesbuddy>" */ +static char *make_cid(const char *cid) { + gsize n; + char *c, *d; + + g_return_val_if_fail(cid != NULL, NULL); + + n = strlen(cid); + g_return_val_if_fail(n > 2, NULL); + + c = g_strndup(cid+1, n-2); + d = g_strdup_printf("cid:%s", c); + + g_free(c); + return d; +} + + +static void im_recv_mime(struct mwConversation *conv, + struct mwGaimPluginData *pd, + const char *data) { + + struct mwIdBlock *idb; + + GHashTable *img_by_cid; + GList *images; + + GString *str; + + GaimMimeDocument *doc; + const GList *parts; + + idb = mwConversation_getTarget(conv); + + img_by_cid = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + images = NULL; + + /* don't want the contained string to ever be NULL */ + str = g_string_new(""); + + doc = gaim_mime_document_parse(data); + + /* handle all the MIME parts */ + parts = gaim_mime_document_get_parts(doc); + for(; parts; parts = parts->next) { + GaimMimePart *part = parts->data; + const char *type; + + type = gaim_mime_part_get_field(part, "content-type"); + DEBUG_INFO("MIME part Content-Type: %s\n", NSTR(type)); + + if(! type) { + ; /* feh */ + + } else if(g_str_has_prefix(type, "image")) { + /* put images into the image store */ + + char *d_dat; + gsize d_len; + char *cid; + int img; + + /* obtain and unencode the data */ + gaim_mime_part_get_data_decoded(part, &d_dat, &d_len); + + /* look up the content id */ + cid = (char *) gaim_mime_part_get_field(part, "Content-ID"); + cid = make_cid(cid); + + /* add image to the gaim image store */ + img = gaim_imgstore_add(d_dat, d_len, cid); + + /* map the cid to the image store identifier */ + g_hash_table_insert(img_by_cid, cid, GINT_TO_POINTER(img)); + + /* recall the image for dereferencing later */ + images = g_list_append(images, GINT_TO_POINTER(img)); + + } else if(g_str_has_prefix(type, "text")) { + + /* concatenate all the text parts together */ + char *data, *txt; + gsize len; + + gaim_mime_part_get_data_decoded(part, &data, &len); + txt = gaim_utf8_try_convert(data); + g_string_append(str, txt); + g_free(txt); + } + } + + gaim_mime_document_free(doc); + + { /* replace each IMG tag's SRC attribute with an ID attribute. This + actually modifies the contents of str */ + GData *attribs; + char *start, *end; + char *tmp = str->str; + + while(*tmp && gaim_markup_find_tag("img", tmp, (const char **) &start, + (const char **) &end, &attribs)) { + + char *alt, *align, *border, *src; + int img; + + alt = g_datalist_get_data(&attribs, "alt"); + align = g_datalist_get_data(&attribs, "align"); + border = g_datalist_get_data(&attribs, "border"); + src = g_datalist_get_data(&attribs, "src"); + + img = GPOINTER_TO_INT(g_hash_table_lookup(img_by_cid, src)); + if(img) { + GString *atstr; + gsize len = (end - start); + gsize mov; + + atstr = g_string_new(""); + if(alt) g_string_append_printf(atstr, " alt=\"%s\"", alt); + if(align) g_string_append_printf(atstr, " align=\"%s\"", align); + if(border) g_string_append_printf(atstr, " border=\"%s\"", border); + + mov = g_snprintf(start, len, "str, img); + while(mov < len) start[mov++] = ' '; + + g_string_free(atstr, TRUE); + } + + g_datalist_clear(&attribs); + tmp = end + 1; + } + } + + /* actually display the message */ + serv_got_im(pd->gc, idb->user, str->str, 0, time(NULL)); + + g_string_free(str, TRUE); + + /* clean up the cid table */ + g_hash_table_destroy(img_by_cid); + + /* dereference all the imgages */ + while(images) { + gaim_imgstore_unref(GPOINTER_TO_INT(images->data)); + images = g_list_delete_link(images, images); + } +} + + +static void mw_conversation_recv(struct mwConversation *conv, + enum mwImSendType type, + gconstpointer msg) { + struct mwServiceIm *srvc; + struct mwSession *session; + struct mwGaimPluginData *pd; + + srvc = mwConversation_getService(conv); + session = mwService_getSession(MW_SERVICE(srvc)); + pd = mwSession_getClientData(session); + + switch(type) { + case mwImSend_PLAIN: + im_recv_text(conv, pd, msg); + break; + + case mwImSend_TYPING: + im_recv_typing(conv, pd, !! msg); + break; + + case mwImSend_HTML: + im_recv_html(conv, pd, msg); + break; + + case mwImSend_SUBJECT: + im_recv_subj(conv, pd, msg); + break; + + case mwImSend_MIME: + im_recv_mime(conv, pd, msg); + break; + + default: + DEBUG_INFO("conversation received strange type, 0x%04x\n", type); + ; /* erm... */ + } +} + + +#if 0 +/* this will be appropriate when meanwhile supports the Place service */ +static void mw_place_invite(struct mwConversation *conv, + const char *message, + const char *title, const char *name) { + struct mwServiceIm *srvc; + struct mwSession *session; + struct mwGaimPluginData *pd; + + struct mwIdBlock *idb; + GHashTable *ht; + + srvc = mwConversation_getService(conv); + session = mwService_getSession(MW_SERVICE(srvc)); + pd = mwSession_getClientData(session); + + idb = mwConversation_getTarget(conv); + + ht = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free); + g_hash_table_insert(ht, CHAT_KEY_CREATOR, g_strdup(idb->user)); + g_hash_table_insert(ht, CHAT_KEY_NAME, g_strdup(name)); + g_hash_table_insert(ht, CHAT_KEY_TOPIC, g_strdup(title)); + g_hash_table_insert(ht, CHAT_KEY_INVITE, g_strdup(message)); + + serv_got_chat_invite(pd->gc, title, idb->user, message, ht); +} +#endif + + +static void mw_im_clear(struct mwServiceIm *srvc) { + ; +} + + +static struct mwImHandler mw_im_handler = { + .conversation_opened = mw_conversation_opened, + .conversation_closed = mw_conversation_closed, + .conversation_recv = mw_conversation_recv, + .place_invite = NULL, /* = mw_place_invite, */ + .clear = mw_im_clear, +}; + + +static struct mwServiceIm *mw_srvc_im_new(struct mwSession *s) { + struct mwServiceIm *srvc; + srvc = mwServiceIm_new(s, &mw_im_handler); + mwServiceIm_setClientType(srvc, mwImClient_NOTESBUDDY); + return srvc; +} + + +static struct mwServiceResolve *mw_srvc_resolve_new(struct mwSession *s) { + struct mwServiceResolve *srvc; + srvc = mwServiceResolve_new(s); + return srvc; +} + + +static struct mwServiceStorage *mw_srvc_store_new(struct mwSession *s) { + struct mwServiceStorage *srvc; + srvc = mwServiceStorage_new(s); + return srvc; +} + + +/** allocate and associate a mwGaimPluginData with a GaimConnection */ +static struct mwGaimPluginData *mwGaimPluginData_new(GaimConnection *gc) { + struct mwGaimPluginData *pd; + + g_return_val_if_fail(gc != NULL, NULL); + + pd = g_new0(struct mwGaimPluginData, 1); + pd->gc = gc; + pd->session = mwSession_new(&mw_session_handler); + pd->srvc_aware = mw_srvc_aware_new(pd->session); + pd->srvc_conf = mw_srvc_conf_new(pd->session); + pd->srvc_ft = mw_srvc_ft_new(pd->session); + pd->srvc_im = mw_srvc_im_new(pd->session); + pd->srvc_resolve = mw_srvc_resolve_new(pd->session); + pd->srvc_store = mw_srvc_store_new(pd->session); + pd->group_list_map = g_hash_table_new(g_direct_hash, g_direct_equal); + + mwSession_addService(pd->session, MW_SERVICE(pd->srvc_aware)); + mwSession_addService(pd->session, MW_SERVICE(pd->srvc_conf)); + mwSession_addService(pd->session, MW_SERVICE(pd->srvc_ft)); + mwSession_addService(pd->session, MW_SERVICE(pd->srvc_im)); + mwSession_addService(pd->session, MW_SERVICE(pd->srvc_resolve)); + mwSession_addService(pd->session, MW_SERVICE(pd->srvc_store)); + + mwSession_addCipher(pd->session, mwCipher_new_RC2_40(pd->session)); + + mwSession_setClientData(pd->session, pd, NULL); + gc->proto_data = pd; + + return pd; +} + + +static void mwGaimPluginData_free(struct mwGaimPluginData *pd) { + g_return_if_fail(pd != NULL); + + pd->gc->proto_data = NULL; + + mwSession_removeService(pd->session, SERVICE_AWARE); + mwSession_removeService(pd->session, SERVICE_CONFERENCE); + mwSession_removeService(pd->session, SERVICE_IM); + mwSession_removeService(pd->session, SERVICE_RESOLVE); + mwSession_removeService(pd->session, SERVICE_STORAGE); + + mwService_free(MW_SERVICE(pd->srvc_aware)); + mwService_free(MW_SERVICE(pd->srvc_conf)); + mwService_free(MW_SERVICE(pd->srvc_im)); + mwService_free(MW_SERVICE(pd->srvc_resolve)); + mwService_free(MW_SERVICE(pd->srvc_store)); + + mwCipher_free(mwSession_getCipher(pd->session, mwCipher_RC2_40)); + + mwSession_free(pd->session); + + g_hash_table_destroy(pd->group_list_map); + + g_free(pd); +} + + +static const char *mw_prpl_list_icon(GaimAccount *a, GaimBuddy *b) { + /* my little green dude is a chopped up version of the aim running + guy. First, cut off the head and store someplace safe. Then, + take the left-half side of the body and throw it away. Make a + copy of the remaining body, and flip it horizontally. Now attach + the two pieces into an X shape, and drop the head back on the + top, being careful to center it. Then, just change the color + saturation to bring the red down a bit, and voila! */ + + /* then, throw all of that away and use sodipodi to make a new + icon. You know, LIKE A REAL MAN. */ + + return "meanwhile"; +} + + +static void mw_prpl_list_emblems(GaimBuddy *b, + const char **se, const char **sw, + const char **nw, const char **ne) { + + /* we have to add the UC_UNAVAILABLE flag so that Gaim will recognie + certain away states as indicating the buddy is unavailable */ + + if(! GAIM_BUDDY_IS_ONLINE(b)) { + *se = "offline"; + } else if(b->uc == (mwStatus_AWAY /* XXX | UC_UNAVAILABLE */)) { + *se = "away"; + } else if(b->uc == (mwStatus_BUSY /* XXX | UC_UNAVAILABLE */)) { + *se = "dnd"; + } +} + + +static char *mw_prpl_status_text(GaimBuddy *b) { + GaimConnection *gc; + struct mwGaimPluginData *pd; + struct mwAwareIdBlock t = { mwAware_USER, b->name, NULL }; + const char *ret; + + gc = b->account->gc; + pd = gc->proto_data; + + ret = mwServiceAware_getText(pd->srvc_aware, &t); + return (ret)? g_strdup(ret): NULL; +} + + +static const char *status_text(GaimBuddy *b) { + guint status = b->uc; + + if(! GAIM_BUDDY_IS_ONLINE(b) ) { + return MW_STATE_OFFLINE; + + } else if(status == (mwStatus_AWAY /* XXX | UC_UNAVAILABLE */)) { + return MW_STATE_AWAY; + + } else if(status == (mwStatus_BUSY /* XXX | UC_UNAVAILABLE */)) { + return MW_STATE_BUSY; + + } else if(status == mwStatus_IDLE) { + return MW_STATE_IDLE; + + } else if(status == mwStatus_ACTIVE) { + return MW_STATE_ACTIVE; + + } else { + return MW_STATE_UNKNOWN; + } +} + + +static gboolean user_supports(struct mwServiceAware *srvc, + const char *who, guint32 feature) { + + const struct mwAwareAttribute *attr; + struct mwAwareIdBlock idb = { mwAware_USER, (char *) who, NULL }; + + attr = mwServiceAware_getAttribute(srvc, &idb, feature); + return (attr != NULL) && mwAwareAttribute_asBoolean(attr); +} + + +char *user_supports_text(struct mwServiceAware *srvc, const char *who) { + char *feat[] = {NULL, NULL, NULL, NULL, NULL}; + char **f = feat; + + if(user_supports(srvc, who, mwAttribute_AV_PREFS_SET)) { + gboolean mic, speak, video; + + mic = user_supports(srvc, who, mwAttribute_MICROPHONE); + speak = user_supports(srvc, who, mwAttribute_SPEAKERS); + video = user_supports(srvc, who, mwAttribute_VIDEO_CAMERA); + + if(mic) *f++ = "Microphone"; + if(speak) *f++ = "Speakers"; + if(video) *f++ = "Video Camera"; + } + + if(user_supports(srvc, who, mwAttribute_FILE_TRANSFER)) + *f++ = "File Transfer"; + + return (*feat)? g_strjoinv(", ", feat): NULL; + /* jenni loves siege */ +} + + +static char *mw_prpl_tooltip_text(GaimBuddy *b) { + GaimConnection *gc; + struct mwGaimPluginData *pd; + struct mwAwareIdBlock idb = { mwAware_USER, b->name, NULL }; + + GString *str; + const char *tmp; + + gc = b->account->gc; + pd = gc->proto_data; + + str = g_string_new(NULL); + + tmp = status_text(b); + g_string_append_printf(str, "\nStatus: %s", tmp); + + tmp = mwServiceAware_getText(pd->srvc_aware, &idb); + if(tmp) g_string_append_printf(str, "\nMessage: %s", tmp); + + tmp = user_supports_text(pd->srvc_aware, b->name); + if(tmp) { + g_string_append_printf(str, "\nSupports: %s", tmp); + g_free((char *) tmp); + } + + tmp = str->str; + g_string_free(str, FALSE); + return (char *) tmp; +} + + +static GList *mw_prpl_status_types(GaimAccount *acct) { + GList *types = NULL; + GaimStatusType *type; + + type = gaim_status_type_new(GAIM_STATUS_OFFLINE, MW_STATE_OFFLINE, + _("Offline"), TRUE); + types = g_list_append(types, type); + + type = gaim_status_type_new(GAIM_STATUS_ONLINE, MW_STATE_ONLINE, + _("Online"), TRUE); + + type = gaim_status_type_new(GAIM_STATUS_AVAILABLE, MW_STATE_ACTIVE, + _("Active"), TRUE); + gaim_status_type_add_attr(type, MW_STATE_MESSAGE, _("Message"), + gaim_value_new(GAIM_TYPE_STRING)); + types = g_list_append(types, type); + + type = gaim_status_type_new(GAIM_STATUS_AWAY, MW_STATE_AWAY, + _("Away"), TRUE); + gaim_status_type_add_attr(type, MW_STATE_MESSAGE, _("Message"), + gaim_value_new(GAIM_TYPE_STRING)); + types = g_list_append(types, type); + + type = gaim_status_type_new(GAIM_STATUS_UNAVAILABLE, MW_STATE_BUSY, + _("Do Not Disturb"), TRUE); + gaim_status_type_add_attr(type, MW_STATE_MESSAGE, _("Message"), + gaim_value_new(GAIM_TYPE_STRING)); + types = g_list_append(types, type); + + return types; +} + + +#if 0 +static GList *mw_prpl_away_states(GaimConnection *gc) { + GList *l = NULL; + + l = g_list_append(l, MW_STATE_ACTIVE); + l = g_list_append(l, MW_STATE_AWAY); + l = g_list_append(l, MW_STATE_BUSY); + l = g_list_append(l, (char *) GAIM_AWAY_CUSTOM); + + return l; +} +#endif + + +static void conf_create_prompt_cancel(GaimBuddy *buddy, + GaimRequestFields *fields) { + ; +} + + +static void conf_create_prompt_join(GaimBuddy *buddy, + GaimRequestFields *fields) { + GaimAccount *acct; + GaimConnection *gc; + struct mwGaimPluginData *pd; + struct mwServiceConference *srvc; + + GaimRequestField *f; + + const char *topic, *invite; + struct mwConference *conf; + struct mwIdBlock idb = { NULL, NULL }; + + acct = buddy->account; + gc = gaim_account_get_connection(acct); + pd = gc->proto_data; + srvc = pd->srvc_conf; + + f = gaim_request_fields_get_field(fields, CHAT_KEY_TOPIC); + topic = gaim_request_field_string_get_value(f); + + f = gaim_request_fields_get_field(fields, CHAT_KEY_INVITE); + invite = gaim_request_field_string_get_value(f); + + conf = mwConference_new(srvc, topic); + mwConference_open(conf); + + idb.user = buddy->name; + mwConference_invite(conf, &idb, invite); +} + + +static void blist_menu_conf_create(GaimBuddy *buddy, const char *msg) { + + GaimRequestFields *fields; + GaimRequestFieldGroup *g; + GaimRequestField *f; + + GaimAccount *acct; + GaimConnection *gc; + + char *msgA, *msgB; + + g_return_if_fail(buddy != NULL); + + acct = buddy->account; + g_return_if_fail(acct != NULL); + + gc = gaim_account_get_connection(acct); + g_return_if_fail(gc != NULL); + + fields = gaim_request_fields_new(); + + g = gaim_request_field_group_new(NULL); + gaim_request_fields_add_group(fields, g); + + f = gaim_request_field_string_new(CHAT_KEY_TOPIC, "Topic", NULL, FALSE); + gaim_request_field_group_add_field(g, f); + + f = gaim_request_field_string_new(CHAT_KEY_INVITE, "Message", msg, FALSE); + gaim_request_field_group_add_field(g, f); + + msgA = ("Create conference with user"); + msgB = ("Please enter a topic for the new conference, and an invitation" + " message to be sent to %s"); + msgB = g_strdup_printf(msgB, buddy->name); + + gaim_request_fields(gc, "New Conference", + msgA, msgB, fields, + "Create", G_CALLBACK(conf_create_prompt_join), + "Cancel", G_CALLBACK(conf_create_prompt_cancel), + buddy); + g_free(msgB); +} + + +static void conf_select_prompt_cancel(GaimBuddy *buddy, + GaimRequestFields *fields) { + ; +} + + +static void conf_select_prompt_invite(GaimBuddy *buddy, + GaimRequestFields *fields) { + GaimRequestField *f; + const GList *l; + const char *msg; + + f = gaim_request_fields_get_field(fields, CHAT_KEY_INVITE); + msg = gaim_request_field_string_get_value(f); + + f = gaim_request_fields_get_field(fields, "conf"); + l = gaim_request_field_list_get_selected(f); + + if(l) { + gpointer d = gaim_request_field_list_get_data(f, l->data); + + if(GPOINTER_TO_INT(d) == 0x01) { + blist_menu_conf_create(buddy, msg); + + } else { + struct mwIdBlock idb = { buddy->name, NULL }; + mwConference_invite(d, &idb, msg); + } + } +} + + +static void blist_menu_conf_list(GaimBuddy *buddy, + GList *confs) { + + GaimRequestFields *fields; + GaimRequestFieldGroup *g; + GaimRequestField *f; + + GaimAccount *acct; + GaimConnection *gc; + + char *msgA, *msgB; + + acct = buddy->account; + g_return_if_fail(acct != NULL); + + gc = gaim_account_get_connection(acct); + g_return_if_fail(gc != NULL); + + fields = gaim_request_fields_new(); + + g = gaim_request_field_group_new(NULL); + gaim_request_fields_add_group(fields, g); + + f = gaim_request_field_list_new("conf", "Available Conferences"); + gaim_request_field_list_set_multi_select(f, FALSE); + for(; confs; confs = confs->next) { + struct mwConference *c = confs->data; + gaim_request_field_list_add(f, mwConference_getTitle(c), c); + } + gaim_request_field_list_add(f, "Create New Conference...", + GINT_TO_POINTER(0x01)); + gaim_request_field_group_add_field(g, f); + + f = gaim_request_field_string_new(CHAT_KEY_INVITE, "Message", NULL, FALSE); + gaim_request_field_group_add_field(g, f); + + msgA = "Invite user to a conference"; + msgB = ("Select a conference from the list below to send an invite to" + " user %s. Select \"Create New Conference\" if you'd like to" + " create a new conference to invite this user to."); + msgB = g_strdup_printf(msgB, buddy->name); + + gaim_request_fields(gc, "Invite to Conference", + msgA, msgB, fields, + "Invite", G_CALLBACK(conf_select_prompt_invite), + "Cancel", G_CALLBACK(conf_select_prompt_cancel), + buddy); + g_free(msgB); +} + + +static void blist_menu_conf(GaimBlistNode *node, gpointer data) { + GaimBuddy *buddy = (GaimBuddy *) node; + GaimAccount *acct; + GaimConnection *gc; + struct mwGaimPluginData *pd; + GList *l; + + g_return_if_fail(node != NULL); + g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node)); + + acct = buddy->account; + g_return_if_fail(acct != NULL); + + gc = gaim_account_get_connection(acct); + g_return_if_fail(gc != NULL); + + pd = gc->proto_data; + g_return_if_fail(pd != NULL); + + /* @todo prompt for which conference to join + + - get a list of all conferences on this session + - if none, prompt to create one, and invite buddy to it + - else, prompt to select a conference or create one + */ + + l = mwServiceConference_getConferences(pd->srvc_conf); + if(l) { + blist_menu_conf_list(buddy, l); + g_list_free(l); + + } else { + blist_menu_conf_create(buddy, NULL); + } +} + + +static GList *mw_prpl_blist_node_menu(GaimBlistNode *node) { + GList *l = NULL; + GaimBlistNodeAction *act; + + if(! GAIM_BLIST_NODE_IS_BUDDY(node)) + return l; + + l = g_list_append(l, NULL); + + act = gaim_blist_node_action_new("Invite to Conference...", + blist_menu_conf, NULL, NULL); + l = g_list_append(l, act); + + /** note: this never gets called for a GaimGroup, have to use the + blist-node-extended-menu signal for that. The function + blist_node_menu_cb is assigned to this signal in the function + services_starting */ + + return l; +} + + +static GList *mw_prpl_chat_info(GaimConnection *gc) { + GList *l = NULL; + struct proto_chat_entry *pce; + + pce = g_new0(struct proto_chat_entry, 1); + pce->label = "Topic:"; + pce->identifier = CHAT_KEY_TOPIC; + l = g_list_append(l, pce); + + return l; +} + + +static GHashTable *mw_prpl_chat_info_defaults(GaimConnection *gc, + const char *name) { + GHashTable *table; + + g_return_val_if_fail(gc != NULL, NULL); + + DEBUG_INFO("mw_prpl_chat_info_defaults for %s\n", NSTR(name)); + + table = g_hash_table_new_full(g_str_hash, g_str_equal, + NULL, g_free); + + g_hash_table_insert(table, CHAT_KEY_NAME, g_strdup(name)); + g_hash_table_insert(table, CHAT_KEY_INVITE, NULL); + + return table; +} + + +static void mw_prpl_login(GaimAccount *acct, GaimStatus *stat); + + +static void prompt_host_cancel_cb(GaimConnection *gc) { + gaim_connection_error(gc, "No Sametime Community Server specified"); +} + + +static void prompt_host_ok_cb(GaimConnection *gc, const char *host) { + if(host && *host) { + GaimAccount *acct; + GaimPresence *pres; + GaimStatus *stat; + + acct = gaim_connection_get_account(gc); + gaim_account_set_string(acct, MW_KEY_HOST, host); + + pres = gaim_account_get_presence(acct); + stat = gaim_presence_get_active_status(pres); + + mw_prpl_login(acct, stat); + + } else { + prompt_host_cancel_cb(gc); + } +} + + +static void prompt_host(GaimConnection *gc) { + GaimAccount *acct; + char *msg; + + acct = gaim_connection_get_account(gc); + msg = ("No host or IP address has been configured for the" + " Meanwhile account %s. Please enter one below to" + " continue logging in."); + msg = g_strdup_printf(msg, NSTR(gaim_account_get_username(acct))); + + gaim_request_input(gc, "Meanwhile Connection Setup", + "No Sametime Community Server Specified", msg, + MW_PLUGIN_DEFAULT_HOST, FALSE, FALSE, NULL, + "Connect", G_CALLBACK(prompt_host_ok_cb), + "Cancel", G_CALLBACK(prompt_host_cancel_cb), + gc); + + g_free(msg); +} + + +static void mw_prpl_login(GaimAccount *account, GaimStatus *stat) { + GaimConnection *gc; + struct mwGaimPluginData *pd; + + char *user, *pass, *host; + guint port; + + gc = gaim_account_get_connection(account); + pd = mwGaimPluginData_new(gc); + + /* while we do support images, the default is to not offer it */ + gc->flags |= GAIM_CONNECTION_NO_IMAGES; + + user = g_strdup(gaim_account_get_username(account)); + pass = (char *) gaim_account_get_password(account); + + DEBUG_INFO("mw_prpl_login\n"); + +#if 1 + host = strrchr(user, ':'); + if(host) { + /* annoying user split from 1.2.0, need to undo it */ + *host++ = '\0'; + gaim_account_set_string(account, MW_KEY_HOST, host); + gaim_account_set_username(account, user); + + } else { + host = (char *) gaim_account_get_string(account, MW_KEY_HOST, + MW_PLUGIN_DEFAULT_HOST); + } + +#else + /* the 1.2.0 way to obtain the host string from an account split. I + didn't like this, it didn't solve a problem, but it created a + few. The above code undoes it. */ + host = strrchr(user, ':'); + if(host) *host++ = '\0'; + + if(! host) { + const char *h; + char *t; + + /* for those without the host string, let's see if they have host + specified in the account setting instead. */ + + h = gaim_account_get_string(account, MW_KEY_HOST, MW_PLUGIN_DEFAULT_HOST); + if(h) { + t = g_strdup_printf("%s:%s", user, h); + gaim_account_set_username(account, t); + g_free(t); + host = (char *) h; + } + } + + /* de-uglify */ + if(! gaim_account_get_alias(account)) + gaim_account_set_alias(account, user); +#endif + + if(! host || ! *host) { + /* somehow, we don't have a host to connect to. Well, we need one + to actually continue, so let's ask the user directly. */ + prompt_host(gc); + return; + } + + port = gaim_account_get_int(account, MW_KEY_PORT, MW_PLUGIN_DEFAULT_PORT); + + DEBUG_INFO("user: '%s'\n", user); + DEBUG_INFO("host: '%s'\n", host); + DEBUG_INFO("port: %u\n", port); + + mwSession_setProperty(pd->session, SESSION_NO_SECRET, + (char *) no_secret, NULL); + mwSession_setProperty(pd->session, mwSession_AUTH_USER_ID, user, g_free); + mwSession_setProperty(pd->session, mwSession_AUTH_PASSWORD, pass, NULL); + mwSession_setProperty(pd->session, mwSession_CLIENT_TYPE_ID, + GUINT_TO_POINTER(MW_CLIENT_TYPE_ID), NULL); + + gaim_connection_update_progress(gc, "Connecting", 1, MW_CONNECT_STEPS); + + if(gaim_proxy_connect(account, host, port, connect_cb, pd)) { + gaim_connection_error(gc, "Unable to connect to host"); + } +} + + +static void mw_prpl_close(GaimConnection *gc) { + struct mwGaimPluginData *pd; + + g_return_if_fail(gc != NULL); + + pd = gc->proto_data; + g_return_if_fail(pd != NULL); + + /* get rid of the blist save timeout */ + if(pd->save_event) { + gaim_timeout_remove(pd->save_event); + pd->save_event = 0; + blist_store(pd); + } + + /* stop the session */ + mwSession_stop(pd->session, 0x00); + + /* no longer necessary */ + gc->proto_data = NULL; + + /* stop watching the socket */ + if(gc->inpa) { + gaim_input_remove(gc->inpa); + gc->inpa = 0; + } + + /* clean up the rest */ + mwGaimPluginData_free(pd); +} + + +static char *im_mime_content_id() { + const char *c = "%03x@%05xmeanwhile"; + srand(time(0) ^ rand()); + return g_strdup_printf(c, rand() & 0xfff, rand() & 0xfffff); +} + + +static char *im_mime_content_type() { + const char *c = "multipart/related; boundary=related_MW%03x_%04x"; + srand(time(0) ^ rand()); + return g_strdup_printf(c, rand() & 0xfff, rand() & 0xffff); +} + + +static const char *im_mime_img_content_type(GaimStoredImage *img) { + const char *fn = gaim_imgstore_get_filename(img); + + fn = strrchr(fn, '.'); + if(! fn) { + return "image"; + + } else if(! strcmp(".png", fn)) { + return "image/png"; + + } else if(! strcmp(".jpg", fn)) { + return "image/jpeg"; + + } else if(! strcmp(".jpeg", fn)) { + return "image/jpeg"; + + } else if(! strcmp(".gif", fn)) { + return "image/gif"; + + } else { + return "image"; + } +} + + +static char *im_mime_img_content_disp(GaimStoredImage *img) { + const char *fn = gaim_imgstore_get_filename(img); + return g_strdup_printf("attachment; filename=\"%s\"", fn); +} + + +static char *im_mime_convert(const char *message) { + GString *str; + GaimMimeDocument *doc; + GaimMimePart *part; + + GData *attr; + char *tmp, *start, *end; + + str = g_string_new(NULL); + + doc = gaim_mime_document_new(); + + gaim_mime_document_set_field(doc, "Mime-Version", "1.0"); + gaim_mime_document_set_field(doc, "Content-Disposition", "inline"); + + tmp = im_mime_content_type(); + gaim_mime_document_set_field(doc, "Content-Type", tmp); + g_free(tmp); + + tmp = (char *) message; + while(*tmp && gaim_markup_find_tag("img", tmp, (const char **) &start, + (const char **) &end, &attr)) { + char *id; + GaimStoredImage *img = NULL; + + gsize len = (start - tmp); + + /* append the in-between-tags text */ + if(len) g_string_append_len(str, tmp, len); + + /* find the imgstore data by the id tag */ + id = g_datalist_get_data(&attr, "id"); + if(id && *id) + img = gaim_imgstore_get(atoi(id)); + + if(img) { + char *cid; + gpointer data; + size_t size; + + part = gaim_mime_part_new(doc); + + data = im_mime_img_content_disp(img); + gaim_mime_part_set_field(part, "Content-Disposition", data); + g_free(data); + + cid = im_mime_content_id(); + data = g_strdup_printf("<%s>", cid); + gaim_mime_part_set_field(part, "Content-ID", data); + g_free(data); + + gaim_mime_part_set_field(part, "Content-transfer-encoding", "base64"); + gaim_mime_part_set_field(part, "Content-Type", + im_mime_img_content_type(img)); + + + /* obtain and base64 encode the image data, and put it in the + mime part */ + data = gaim_imgstore_get_data(img); + size = gaim_imgstore_get_size(img); + data = gaim_base64_encode(data, (gsize) size); + gaim_mime_part_set_data(part, data); + g_free(data); + + /* append the modified tag */ + g_string_append_printf(str, "", cid); + g_free(cid); + + } else { + /* append the literal image tag, since we couldn't find a + relative imgstore object */ + gsize len = (end - start) + 1; + g_string_append_len(str, start, len); + } + + g_datalist_clear(&attr); + tmp = end + 1; + } + + /* append left-overs */ + g_string_append(str, tmp); + + part = gaim_mime_part_new(doc); + gaim_mime_part_set_field(part, "Content-Type", "text/html"); + gaim_mime_part_set_field(part, "Content-Disposition", "inline"); + gaim_mime_part_set_field(part, "Content-Transfer-Encoding", "8bit"); + + gaim_mime_part_set_data(part, str->str); + g_string_free(str, TRUE); + + str = g_string_new(NULL); + gaim_mime_document_write(doc, str); + tmp = str->str; + g_string_free(str, FALSE); + + return tmp; +} + + +static int mw_prpl_send_im(GaimConnection *gc, + const char *name, + const char *message, + GaimConvImFlags flags) { + + struct mwGaimPluginData *pd; + struct mwIdBlock who = { (char *) name, NULL }; + struct mwConversation *conv; + + g_return_val_if_fail(gc != NULL, 0); + pd = gc->proto_data; + + g_return_val_if_fail(pd != NULL, 0); + + conv = mwServiceIm_getConversation(pd->srvc_im, &who); + + /* this detection of features to determine how to send the message + (plain, html, or mime) is flawed because the other end of the + conversation could close their channel at any time, rendering any + existing formatting in an outgoing message innapropriate. The end + result is that it may be possible that the other side of the + conversation will receive a plaintext message with html contents, + which is bad. I'm not sure how to fix this correctly. */ + + /* @todo support chunking messages over a certain size into multiple + smaller messages */ + + if(strstr(message, " conversion */ + msg = gaim_strdup_withhtml(message); + ret = mwConversation_send(conv, mwImSend_HTML, msg); + + } else { + ret = mwConversation_send(conv, mwImSend_PLAIN, message); + } + + g_free(msg); + return !ret; + } + + /* queue up the message */ + convo_queue(conv, mwImSend_PLAIN, message); + + if(! mwConversation_isPending(conv)) + mwConversation_open(conv); + + return 1; +} + + +static int mw_prpl_send_typing(GaimConnection *gc, const char *name, + int typing) { + + struct mwGaimPluginData *pd; + struct mwIdBlock who = { (char *) name, NULL }; + struct mwConversation *conv; + + gpointer t = GINT_TO_POINTER(!! typing); + + g_return_val_if_fail(gc != NULL, 0); + pd = gc->proto_data; + + g_return_val_if_fail(pd != NULL, 0); + + conv = mwServiceIm_getConversation(pd->srvc_im, &who); + + if(mwConversation_isOpen(conv)) + return ! mwConversation_send(conv, mwImSend_TYPING, t); + + if(typing) { + /* let's only open a channel for typing, not for not-typing. + Otherwise two users in psychic mode will continually open + conversations to each other, never able to get rid of them, as + when the other person closes, it psychicaly opens again */ + + convo_queue(conv, mwImSend_TYPING, t); + + if(! mwConversation_isPending(conv)) + mwConversation_open(conv); + } + + return 1; +} + + +static void mw_prpl_get_info(GaimConnection *gc, const char *who) { + + struct mwGaimPluginData *pd; + struct mwAwareIdBlock idb = { mwAware_USER, (char *) who, NULL }; + + GaimAccount *acct; + GaimBuddy *b; + + GString *str; + const char *tmp; + guint32 type; + + pd = gc->proto_data; + + acct = gaim_connection_get_account(gc); + b = gaim_find_buddy(acct, who); + + g_return_if_fail(b != NULL); + + str = g_string_new(NULL); + + g_string_append_printf(str, "User ID: %s
", b->name); + + if(b->server_alias) { + g_string_append_printf(str, "Full Name: %s
", + b->server_alias); + } + + type = gaim_blist_node_get_int((GaimBlistNode *) b, BUDDY_KEY_CLIENT); + if(type) { + g_string_append(str, "Last Known Client: "); + + tmp = mwLoginType_getName(type); + if(tmp) { + g_string_append(str, tmp); + g_string_append(str, "
"); + + } else { + g_string_append_printf(str, "Unknown (0x%04x)
", type); + } + } + + tmp = user_supports_text(pd->srvc_aware, who); + if(tmp) { + g_string_append_printf(str, "Supports: %s
", tmp); + g_free((char *) tmp); + } + + tmp = status_text(b); + g_string_append_printf(str, "Status: %s", tmp); + + g_string_append(str, "
"); + + tmp = mwServiceAware_getText(pd->srvc_aware, &idb); + g_string_append(str, tmp); + + /* @todo emit a signal to allow a plugin to override the display of + this notification, so that it can create its own */ + + gaim_notify_userinfo(gc, who, "Buddy Information", + "Meanwhile User Status", NULL, str->str, NULL, NULL); + + g_string_free(str, TRUE); +} + + +#if 0 +static void mw_prpl_set_away(GaimConnection *gc, + const char *state, + const char *message) { + GaimAccount *acct; + struct mwSession *session; + struct mwUserStatus stat; + + acct = gaim_connection_get_account(gc); + g_return_if_fail(acct != NULL); + + session = gc_to_session(gc); + g_return_if_fail(session != NULL); + + /* get a working copy of the current status */ + mwUserStatus_clone(&stat, mwSession_getUserStatus(session)); + + /* determine the state */ + if(state) { + if(! strcmp(state, GAIM_AWAY_CUSTOM)) { + if(message) { + stat.status = mwStatus_AWAY; + } else { + stat.status = mwStatus_ACTIVE; + } + + } else if(! strcmp(state, MW_STATE_AWAY)) { + stat.status = mwStatus_AWAY; + + } else if(! strcmp(state, MW_STATE_BUSY)) { + stat.status = mwStatus_BUSY; + + } else if(! strcmp(state, MW_STATE_ACTIVE)) { + stat.status = mwStatus_ACTIVE; + } + + } else { + stat.status = mwStatus_ACTIVE; + } + + /* determine the message */ + if(! message) { + switch(stat.status) { + case mwStatus_AWAY: + message = gaim_account_get_string(acct, MW_KEY_AWAY_MSG, + MW_PLUGIN_DEFAULT_AWAY_MSG); + break; + + case mwStatus_BUSY: + message = gaim_account_get_string(acct, MW_KEY_BUSY_MSG, + MW_PLUGIN_DEFAULT_BUSY_MSG); + break; + + case mwStatus_ACTIVE: + message = gaim_account_get_string(acct, MW_KEY_ACTIVE_MSG, + MW_PLUGIN_DEFAULT_ACTIVE_MSG); + stat.time = 0; + break; + } + } + + if(message) { + /* all the possible non-NULL values of message up to this point + are const, so we don't need to free them */ + message = gaim_markup_strip_html(message); + } + + /* out with the old */ + g_free(stat.desc); + g_free(gc->away); + + /* in with the new */ + stat.desc = (char *) message; + gc->away = g_strdup(message); + + mwSession_setUserStatus(session, &stat); + mwUserStatus_clear(&stat); +} +#endif + + +static void mw_prpl_set_idle(GaimConnection *gc, int time) { + struct mwSession *session; + struct mwUserStatus stat; + + session = gc_to_session(gc); + g_return_if_fail(session != NULL); + + mwUserStatus_clone(&stat, mwSession_getUserStatus(session)); + + if(time > 0 && stat.status == mwStatus_ACTIVE) { + stat.status = mwStatus_IDLE; + + } else if(time == 0 && stat.status == mwStatus_IDLE) { + stat.status = mwStatus_ACTIVE; + } + + /** @todo actually put the idle time in the user status */ + + mwSession_setUserStatus(session, &stat); + mwUserStatus_clear(&stat); +} + + +static void add_resolved_done(const char *id, const char *name, + GaimBuddy *buddy) { + GaimAccount *acct; + GaimConnection *gc; + struct mwGaimPluginData *pd; + + g_return_if_fail(id != NULL); + + g_return_if_fail(buddy != NULL); + acct = buddy->account; + + g_return_if_fail(acct != NULL); + gc = gaim_account_get_connection(acct); + + g_return_if_fail(gc != NULL); + pd = gc->proto_data; + + gaim_blist_rename_buddy(buddy, id); + + gaim_blist_server_alias_buddy(buddy, name); + gaim_blist_node_set_string((GaimBlistNode *) buddy, BUDDY_KEY_NAME, name); + + buddy_add(pd, buddy); +} + + +static void multi_resolved_cleanup(GaimRequestFields *fields) { + + GaimRequestField *f; + const GList *l; + + f = gaim_request_fields_get_field(fields, "user"); + l = gaim_request_field_list_get_items(f); + + for(; l; l = l->next) { + const char *i = l->data; + struct resolved_id *res; + + res = gaim_request_field_list_get_data(f, i); + + g_free(res->id); + g_free(res->name); + g_free(res); + } +} + + +static void multi_resolved_cancel(GaimBuddy *buddy, + GaimRequestFields *fields) { + GaimConnection *gc; + struct mwGaimPluginData *pd; + + gc = gaim_account_get_connection(buddy->account); + pd = gc->proto_data; + + gaim_blist_remove_buddy(buddy); + multi_resolved_cleanup(fields); + + blist_schedule(pd); +} + + +static void multi_resolved_cb(GaimBuddy *buddy, + GaimRequestFields *fields) { + GaimRequestField *f; + const GList *l; + + f = gaim_request_fields_get_field(fields, "user"); + l = gaim_request_field_list_get_selected(f); + + if(l) { + const char *i = l->data; + struct resolved_id *res; + + res = gaim_request_field_list_get_data(f, i); + + add_resolved_done(res->id, res->name, buddy); + multi_resolved_cleanup(fields); + + } else { + multi_resolved_cancel(buddy, fields); + } +} + + +static void multi_resolved_query(struct mwResolveResult *result, + GaimBuddy *buddy) { + GaimRequestFields *fields; + GaimRequestFieldGroup *g; + GaimRequestField *f; + GList *l; + char *msgA, *msgB; + + GaimAccount *acct; + GaimConnection *gc; + + g_return_if_fail(buddy != NULL); + + acct = buddy->account; + g_return_if_fail(acct != NULL); + + gc = gaim_account_get_connection(acct); + g_return_if_fail(gc != NULL); + + fields = gaim_request_fields_new(); + + g = gaim_request_field_group_new(NULL); + + /* note that Gaim segfaults if you don't add the group to the fields + before you add a required field to the group. Feh. */ + gaim_request_fields_add_group(fields, g); + + f = gaim_request_field_list_new("user", "Possible Matches"); + gaim_request_field_list_set_multi_select(f, FALSE); + gaim_request_field_set_required(f, TRUE); + + for(l = result->matches; l; l = l->next) { + struct mwResolveMatch *match = l->data; + struct resolved_id *res = g_new0(struct resolved_id, 1); + char *label; + + res->id = g_strdup(match->id); + res->name = g_strdup(match->name); + + /* fixes bug 1178603 by making the selection label a combination + of the full name and the user id. Problems arrise when multiple + entries have identical labels */ + label = g_strdup_printf("%s (%s)", NSTR(res->name), NSTR(res->id)); + gaim_request_field_list_add(f, label, res); + g_free(label); + } + + gaim_request_field_group_add_field(g, f); + + msgA = ("An ambiguous user ID was entered"); + msgB = ("The identifier '%s' may possibly refer to any of the following" + " users. Please select the correct user from the list below to" + " add them to your buddy list."); + msgB = g_strdup_printf(msgB, result->name); + + gaim_request_fields(gc, "Select User to Add", + msgA, msgB, fields, + "Add User", G_CALLBACK(multi_resolved_cb), + "Cancel", G_CALLBACK(multi_resolved_cancel), + buddy); + g_free(msgB); +} + + +static void add_buddy_resolved(struct mwServiceResolve *srvc, + guint32 id, guint32 code, GList *results, + gpointer b) { + + struct mwResolveResult *res = NULL; + GaimBuddy *buddy = b; + GaimConnection *gc; + struct mwGaimPluginData *pd; + + gc = gaim_account_get_connection(buddy->account); + pd = gc->proto_data; + + if(results) + res = results->data; + + if(!code && res && res->matches) { + if(g_list_length(res->matches) == 1) { + struct mwResolveMatch *match = res->matches->data; + + DEBUG_INFO("searched for %s, got only %s\n", + NSTR(res->name), NSTR(match->id)); + + /* only one? that might be the right one! */ + if(strcmp(res->name, match->id)) { + /* uh oh, the single result isn't identical to the search + term, better safe then sorry, so let's make sure it's who + the user meant to add */ + multi_resolved_query(res, buddy); + + } else { + /* same person, add 'em */ + add_resolved_done(match->id, match->name, buddy); + } + + } else { + /* prompt user if more than one match was returned */ + multi_resolved_query(res, buddy); + } + + return; + } + + /* fall-through indicates that we couldn't find a matching user in + the resolve service (ether error or zero results), so we remove + this buddy */ + + DEBUG_INFO("no such buddy in community\n"); + gaim_blist_remove_buddy(buddy); + blist_schedule(pd); + + if(res && res->name) { + /* compose and display an error message */ + char *msgA, *msgB; + + msgA = "Unable to add user: user not found"; + + msgB = ("The identifier '%s' did not match any users in your" + " Sametime community. This entry has been removed from" + " your buddy list."); + msgB = g_strdup_printf(msgB, NSTR(res->name)); + + gaim_notify_error(gc, "Unable to add user", msgA, msgB); + + g_free(msgB); + } +} + + +static void mw_prpl_add_buddy(GaimConnection *gc, + GaimBuddy *buddy, + GaimGroup *group) { + + struct mwGaimPluginData *pd; + struct mwServiceResolve *srvc; + GList *query; + enum mwResolveFlag flags; + guint32 req; + + pd = gc->proto_data; + srvc = pd->srvc_resolve; + + query = g_list_prepend(NULL, buddy->name); + flags = mwResolveFlag_FIRST | mwResolveFlag_USERS; + + req = mwServiceResolve_resolve(srvc, query, flags, add_buddy_resolved, + buddy, NULL); + g_list_free(query); + + if(req == SEARCH_ERROR) { + gaim_blist_remove_buddy(buddy); + blist_schedule(pd); + } +} + + +static void foreach_add_buddies(GaimGroup *group, GList *buddies, + struct mwGaimPluginData *pd) { + + struct mwAwareList *list; + + list = list_ensure(pd, group); + mwAwareList_addAware(list, buddies); + g_list_free(buddies); +} + + +static void mw_prpl_add_buddies(GaimConnection *gc, + GList *buddies, + GList *groups) { + + /** @todo make this use a single call to each mwAwareList */ + + struct mwGaimPluginData *pd; + GHashTable *group_sets; + struct mwAwareIdBlock *idbs, *idb; + + pd = gc->proto_data; + + /* map GaimGroup:GList of mwAwareIdBlock */ + group_sets = g_hash_table_new(g_direct_hash, g_direct_equal); + + /* bunch of mwAwareIdBlock allocated at once, free'd at once */ + idb = idbs = g_new(struct mwAwareIdBlock, g_list_length(buddies)); + + /* first pass collects mwAwareIdBlock lists for each group */ + for(; buddies; buddies = buddies->next) { + GaimBuddy *b = buddies->data; + GaimGroup *g; + const char *fn; + GList *l; + + /* nab the saved server alias and stick it on the buddy */ + fn = gaim_blist_node_get_string((GaimBlistNode *) b, BUDDY_KEY_NAME); + gaim_blist_server_alias_buddy(b, fn); + + /* convert GaimBuddy into a mwAwareIdBlock */ + idb->type = mwAware_USER; + idb->user = (char *) b->name; + idb->community = NULL; + + /* put idb into the list associated with the buddy's group */ + g = gaim_find_buddys_group(b); + l = g_hash_table_lookup(group_sets, g); + l = g_list_prepend(l, idb++); + g_hash_table_insert(group_sets, g, l); + } + + /* each group's buddies get added in one shot, and schedule the blist + for saving */ + g_hash_table_foreach(group_sets, (GHFunc) foreach_add_buddies, pd); + blist_schedule(pd); + + /* cleanup */ + g_hash_table_destroy(group_sets); + g_free(idbs); +} + + +static void mw_prpl_remove_buddy(GaimConnection *gc, + GaimBuddy *buddy, GaimGroup *group) { + + struct mwGaimPluginData *pd; + struct mwAwareIdBlock idb = { mwAware_USER, buddy->name, NULL }; + struct mwAwareList *list; + + GList *rem = g_list_prepend(NULL, &idb); + + pd = gc->proto_data; + group = gaim_find_buddys_group(buddy); + list = list_ensure(pd, group); + + mwAwareList_removeAware(list, rem); + blist_schedule(pd); + + g_list_free(rem); +} + + +static void privacy_fill(struct mwPrivacyInfo *priv, + GSList *members) { + + struct mwUserItem *u; + guint count; + + DEBUG_INFO("privacy_fill\n"); + + count = g_slist_length(members); + DEBUG_INFO(" %u (%i) members\n", count, (int) count); + + priv->count = count; + priv->users = g_new0(struct mwUserItem, count); + + while(count--) { + u = priv->users + count; + u->id = members->data; + members = members->next; + } +} + + +static void mw_prpl_set_permit_deny(GaimConnection *gc) { + GaimAccount *acct; + struct mwGaimPluginData *pd; + struct mwSession *session; + + struct mwPrivacyInfo privacy = { + .deny = FALSE, + .count = 0, + .users = NULL, + }; + + g_return_if_fail(gc != NULL); + + acct = gaim_connection_get_account(gc); + g_return_if_fail(acct != NULL); + + pd = gc->proto_data; + g_return_if_fail(pd != NULL); + + session = pd->session; + g_return_if_fail(session != NULL); + + switch(acct->perm_deny) { + case GAIM_PRIVACY_DENY_USERS: + DEBUG_INFO("GAIM_PRIVACY_DENY_USERS\n"); + privacy_fill(&privacy, acct->deny); + /* fall-through */ + + case GAIM_PRIVACY_ALLOW_ALL: + DEBUG_INFO("GAIM_PRIVACY_ALLOW_ALL\n"); + privacy.deny = TRUE; + break; + + case GAIM_PRIVACY_ALLOW_USERS: + DEBUG_INFO("GAIM_PRIVACY_ALLOW_USERS\n"); + privacy_fill(&privacy, acct->permit); + /* fall-through */ + + case GAIM_PRIVACY_DENY_ALL: + DEBUG_INFO("GAIM_PRIVACY_DENY_ALL\n"); + privacy.deny = FALSE; + break; + + default: + DEBUG_INFO("acct->perm_deny is 0x%x\n", acct->perm_deny); + g_return_if_reached(); + } + + mwSession_setPrivacyInfo(session, &privacy); + g_free(privacy.users); +} + + +static void mw_prpl_add_permit(GaimConnection *gc, const char *name) { + mw_prpl_set_permit_deny(gc); +} + + +static void mw_prpl_add_deny(GaimConnection *gc, const char *name) { + mw_prpl_set_permit_deny(gc); +} + + +static void mw_prpl_rem_permit(GaimConnection *gc, const char *name) { + mw_prpl_set_permit_deny(gc); +} + + +static void mw_prpl_rem_deny(GaimConnection *gc, const char *name) { + mw_prpl_set_permit_deny(gc); +} + + +static struct mwConference *conf_find(struct mwServiceConference *srvc, + const char *name) { + GList *l, *ll; + struct mwConference *conf = NULL; + + ll = mwServiceConference_getConferences(srvc); + for(l = ll; l; l = l->next) { + struct mwConference *c = l->data; + if(! strcmp(name, mwConference_getName(c))) { + conf = c; + break; + } + } + g_list_free(ll); + + return conf; +} + + +static void mw_prpl_join_chat(GaimConnection *gc, + GHashTable *components) { + + struct mwGaimPluginData *pd; + struct mwServiceConference *srvc; + struct mwConference *conf = NULL; + char *c, *t; + + pd = gc->proto_data; + srvc = pd->srvc_conf; + + c = g_hash_table_lookup(components, CHAT_KEY_NAME); + t = g_hash_table_lookup(components, CHAT_KEY_TOPIC); + + if(c) conf = conf_find(srvc, c); + + if(conf) { + DEBUG_INFO("accepting conference invitation\n"); + mwConference_accept(conf); + + } else { + DEBUG_INFO("creating new conference\n"); + conf = mwConference_new(srvc, t); + mwConference_open(conf); + } +} + + +static void mw_prpl_reject_chat(GaimConnection *gc, + GHashTable *components) { + + struct mwGaimPluginData *pd; + struct mwServiceConference *srvc; + char *c; + + pd = gc->proto_data; + srvc = pd->srvc_conf; + + c = g_hash_table_lookup(components, CHAT_KEY_NAME); + if(c) { + struct mwConference *conf = conf_find(srvc, c); + if(conf) mwConference_reject(conf, ERR_SUCCESS, "Declined"); + } +} + + +static char *mw_prpl_get_chat_name(GHashTable *components) { + return g_hash_table_lookup(components, CHAT_KEY_NAME); +} + + +static void mw_prpl_chat_invite(GaimConnection *gc, + int id, + const char *invitation, + const char *who) { + + struct mwGaimPluginData *pd; + struct mwConference *conf; + struct mwIdBlock idb = { (char *) who, NULL }; + + pd = gc->proto_data; + + g_return_if_fail(pd != NULL); + conf = ID_TO_CONF(pd, id); + + g_return_if_fail(conf != NULL); + + mwConference_invite(conf, &idb, invitation); +} + + +static void mw_prpl_chat_leave(GaimConnection *gc, + int id) { + + struct mwGaimPluginData *pd; + struct mwConference *conf; + + pd = gc->proto_data; + + g_return_if_fail(pd != NULL); + conf = ID_TO_CONF(pd, id); + + g_return_if_fail(conf != NULL); + + mwConference_destroy(conf, ERR_SUCCESS, "Leaving"); +} + + +static void mw_prpl_chat_whisper(GaimConnection *gc, + int id, + const char *who, + const char *message) { + + mw_prpl_send_im(gc, who, message, 0); +} + + +static int mw_prpl_chat_send(GaimConnection *gc, + int id, + const char *message) { + + struct mwGaimPluginData *pd; + struct mwConference *conf; + + pd = gc->proto_data; + + g_return_val_if_fail(pd != NULL, 0); + conf = ID_TO_CONF(pd, id); + + g_return_val_if_fail(conf != NULL, 0); + + return ! mwConference_sendText(conf, message); +} + + +static void mw_prpl_keepalive(GaimConnection *gc) { + struct mwSession *session; + + g_return_if_fail(gc != NULL); + + session = gc_to_session(gc); + g_return_if_fail(session != NULL); + + mwSession_sendKeepalive(session); +} + + +static void mw_prpl_alias_buddy(GaimConnection *gc, + const char *who, + const char *alias) { + + struct mwGaimPluginData *pd = gc->proto_data; + g_return_if_fail(pd != NULL); + + /* it's a change to the buddy list, so we've gotta reflect that in + the server copy */ + + blist_schedule(pd); +} + + +static void mw_prpl_group_buddy(GaimConnection *gc, + const char *who, + const char *old_group, + const char *new_group) { + + struct mwAwareIdBlock idb = { mwAware_USER, (char *) who, NULL }; + GList *gl = g_list_prepend(NULL, &idb); + + struct mwGaimPluginData *pd = gc->proto_data; + GaimGroup *group; + struct mwAwareList *list; + + /* add who to new_group's aware list */ + group = gaim_find_group(new_group); + list = list_ensure(pd, group); + mwAwareList_addAware(list, gl); + + /* remove who from old_group's aware list */ + group = gaim_find_group(old_group); + list = list_ensure(pd, group); + mwAwareList_removeAware(list, gl); + + g_list_free(gl); + + /* schedule the changes to be saved */ + blist_schedule(pd); +} + + +static void mw_prpl_rename_group(GaimConnection *gc, + const char *old, + GaimGroup *group, + GList *buddies) { + + struct mwGaimPluginData *pd = gc->proto_data; + g_return_if_fail(pd != NULL); + + /* it's a change in the buddy list, so we've gotta reflect that in + the server copy. Also, having this function should prevent all + those buddies from being removed and re-added. We don't really + give a crap what the group is named in Gaim other than to record + that as the group name/alias */ + + blist_schedule(pd); +} + + +static void mw_prpl_buddy_free(GaimBuddy *buddy) { + /* I don't think we have any cleanup for buddies yet */ + ; +} + + +static void mw_prpl_convo_closed(GaimConnection *gc, const char *who) { + struct mwGaimPluginData *pd = gc->proto_data; + struct mwServiceIm *srvc; + struct mwConversation *conv; + struct mwIdBlock idb = { (char *) who, NULL }; + + g_return_if_fail(pd != NULL); + + srvc = pd->srvc_im; + g_return_if_fail(srvc != NULL); + + conv = mwServiceIm_findConversation(srvc, &idb); + if(! conv) return; + + if(mwConversation_isOpen(conv)) + mwConversation_free(conv); +} + + +static const char *mw_prpl_normalize(const GaimAccount *account, + const char *id) { + + /* code elsewhere assumes that the return value points to different + memory than the passed value, but it won't free the normalized + data. wtf? */ + + static char buf[BUF_LEN]; + strncpy(buf, id, sizeof(buf)); + return buf; +} + + +static void mw_prpl_remove_group(GaimConnection *gc, GaimGroup *group) { + struct mwGaimPluginData *pd; + struct mwAwareList *list; + + pd = gc->proto_data; + g_return_if_fail(pd != NULL); + g_return_if_fail(pd->group_list_map != NULL); + + list = g_hash_table_lookup(pd->group_list_map, group); + + if(list) { + g_hash_table_remove(pd->group_list_map, list); + g_hash_table_remove(pd->group_list_map, group); + mwAwareList_free(list); + + blist_schedule(pd); + } +} + + +static gboolean mw_prpl_can_receive_file(GaimConnection *gc, + const char *who) { + struct mwGaimPluginData *pd; + struct mwServiceAware *srvc; + GaimAccount *acct; + + g_return_val_if_fail(gc != NULL, FALSE); + + pd = gc->proto_data; + g_return_val_if_fail(pd != NULL, FALSE); + + srvc = pd->srvc_aware; + g_return_val_if_fail(srvc != NULL, FALSE); + + acct = gaim_connection_get_account(gc); + g_return_val_if_fail(acct != NULL, FALSE); + + return gaim_find_buddy(acct, who) && + user_supports(srvc, who, mwAttribute_FILE_TRANSFER); +} + + +static void ft_outgoing_init(GaimXfer *xfer) { + GaimAccount *acct; + GaimConnection *gc; + + struct mwGaimPluginData *pd; + struct mwServiceFileTransfer *srvc; + struct mwFileTransfer *ft; + + const char *filename; + gsize filesize; + FILE *fp; + + struct mwIdBlock idb = { NULL, NULL }; + + DEBUG_INFO("ft_outgoing_init\n"); + + acct = gaim_xfer_get_account(xfer); + gc = gaim_account_get_connection(acct); + pd = gc->proto_data; + srvc = pd->srvc_ft; + + filename = gaim_xfer_get_local_filename(xfer); + filesize = gaim_xfer_get_size(xfer); + idb.user = xfer->who; + + /* test that we can actually send the file */ + fp = g_fopen(filename, "rb"); + if(! fp) { + char *msg = g_strdup_printf("Error reading %s: \n%s\n", + filename, strerror(errno)); + gaim_xfer_error(gaim_xfer_get_type(xfer), acct, xfer->who, msg); + g_free(msg); + return; + } + fclose(fp); + + { + char *tmp = strrchr(filename, G_DIR_SEPARATOR); + if(tmp++) filename = tmp; + } + + ft = mwFileTransfer_new(srvc, &idb, NULL, filename, filesize); + + gaim_xfer_ref(xfer); + mwFileTransfer_setClientData(ft, xfer, (GDestroyNotify) gaim_xfer_unref); + xfer->data = ft; + + mwFileTransfer_offer(ft); +} + + +static void ft_outgoing_cancel(GaimXfer *xfer) { + struct mwFileTransfer *ft = xfer->data; + if(ft) mwFileTransfer_cancel(ft); +} + + +static void mw_prpl_send_file(GaimConnection *gc, + const char *who, const char *file) { + + /** @todo depends on the meanwhile implementation of the file + transfer service */ + + GaimAccount *acct; + GaimXfer *xfer; + + DEBUG_INFO("mw_prpl_send_file\n"); + + acct = gaim_connection_get_account(gc); + + xfer = gaim_xfer_new(acct, GAIM_XFER_SEND, who); + gaim_xfer_set_init_fnc(xfer, ft_outgoing_init); + gaim_xfer_set_cancel_send_fnc(xfer, ft_outgoing_cancel); + + if(file) { + DEBUG_INFO("file != NULL\n"); + gaim_xfer_request_accepted(xfer, file); + + } else { + DEBUG_INFO("file == NULL\n"); + gaim_xfer_request(xfer); + } +} + + +static GaimPluginProtocolInfo mw_prpl_info = { + .options = OPT_PROTO_IM_IMAGE, + .user_splits = NULL, /*< set in mw_plugin_init */ + .protocol_options = NULL, /*< set in mw_plugin_init */ + .icon_spec = NO_BUDDY_ICONS, + .list_icon = mw_prpl_list_icon, + .list_emblems = mw_prpl_list_emblems, + .status_text = mw_prpl_status_text, + .tooltip_text = mw_prpl_tooltip_text, + .status_types = mw_prpl_status_types, + .blist_node_menu = mw_prpl_blist_node_menu, + .chat_info = mw_prpl_chat_info, + .chat_info_defaults = mw_prpl_chat_info_defaults, + .login = mw_prpl_login, + .close = mw_prpl_close, + .send_im = mw_prpl_send_im, + .set_info = NULL, + .send_typing = mw_prpl_send_typing, + .get_info = mw_prpl_get_info, + .set_idle = mw_prpl_set_idle, + .change_passwd = NULL, + .add_buddy = mw_prpl_add_buddy, + .add_buddies = mw_prpl_add_buddies, + .remove_buddy = mw_prpl_remove_buddy, + .remove_buddies = NULL, + .add_permit = mw_prpl_add_permit, + .add_deny = mw_prpl_add_deny, + .rem_permit = mw_prpl_rem_permit, + .rem_deny = mw_prpl_rem_deny, + .set_permit_deny = mw_prpl_set_permit_deny, + .warn = NULL, + .join_chat = mw_prpl_join_chat, + .reject_chat = mw_prpl_reject_chat, + .get_chat_name = mw_prpl_get_chat_name, + .chat_invite = mw_prpl_chat_invite, + .chat_leave = mw_prpl_chat_leave, + .chat_whisper = mw_prpl_chat_whisper, + .chat_send = mw_prpl_chat_send, + .keepalive = mw_prpl_keepalive, + .register_user = NULL, + .get_cb_info = NULL, + .get_cb_away = NULL, + .alias_buddy = mw_prpl_alias_buddy, + .group_buddy = mw_prpl_group_buddy, + .rename_group = mw_prpl_rename_group, + .buddy_free = mw_prpl_buddy_free, + .convo_closed = mw_prpl_convo_closed, + .normalize = mw_prpl_normalize, + .set_buddy_icon = NULL, + .remove_group = mw_prpl_remove_group, + .get_cb_real_name = NULL, + .set_chat_topic = NULL, + .find_blist_chat = NULL, + .roomlist_get_list = NULL, + .roomlist_expand_category = NULL, + .can_receive_file = mw_prpl_can_receive_file, + .send_file = mw_prpl_send_file, +}; + + +static GaimPluginPrefFrame * +mw_plugin_get_plugin_pref_frame(GaimPlugin *plugin) { + GaimPluginPrefFrame *frame; + GaimPluginPref *pref; + +#if 0 + char *msg; +#endif + + frame = gaim_plugin_pref_frame_new(); + + pref = gaim_plugin_pref_new_with_label("Remotely Stored Buddy List"); + gaim_plugin_pref_frame_add(frame, pref); + + + pref = gaim_plugin_pref_new_with_name(MW_PRPL_OPT_BLIST_ACTION); + gaim_plugin_pref_set_label(pref, "Buddy List Storage Mode"); + + gaim_plugin_pref_set_type(pref, GAIM_PLUGIN_PREF_CHOICE); + gaim_plugin_pref_add_choice(pref, "Local Buddy List Only", + GINT_TO_POINTER(BLIST_CHOICE_NONE)); + gaim_plugin_pref_add_choice(pref, "Merge List from Server", + GINT_TO_POINTER(BLIST_CHOICE_LOAD)); + gaim_plugin_pref_add_choice(pref, "Merge and Save List to Server", + GINT_TO_POINTER(BLIST_CHOICE_SAVE)); + +#if 0 + /* possible ways to handle: + - mark all buddies as NO_SAVE + - load server list, delete all local buddies not in server list + */ + gaim_plugin_pref_add_choice(pref, "Server Buddy List Only", + GINT_TO_POINTER(BLIST_CHOISE_SERVER)); +#endif + + gaim_plugin_pref_frame_add(frame, pref); + + pref = gaim_plugin_pref_new(); + gaim_plugin_pref_set_type(pref, GAIM_PLUGIN_PREF_INFO); + gaim_plugin_pref_set_label(pref, BLIST_WARNING); + gaim_plugin_pref_frame_add(frame, pref); + + pref = gaim_plugin_pref_new_with_label("General Options"); + gaim_plugin_pref_frame_add(frame, pref); + + pref = gaim_plugin_pref_new_with_name(MW_PRPL_OPT_FORCE_LOGIN); + gaim_plugin_pref_set_type(pref, GAIM_PLUGIN_PREF_NONE); + gaim_plugin_pref_set_label(pref, "Force Login (Ignore Login Redirects)"); + gaim_plugin_pref_frame_add(frame, pref); + + pref = gaim_plugin_pref_new_with_name(MW_PRPL_OPT_PSYCHIC); + gaim_plugin_pref_set_type(pref, GAIM_PLUGIN_PREF_NONE); + gaim_plugin_pref_set_label(pref, "Enable Psychic Mode"); + gaim_plugin_pref_frame_add(frame, pref); + + pref = gaim_plugin_pref_new_with_name(MW_PRPL_OPT_SAVE_DYNAMIC); + gaim_plugin_pref_set_type(pref, GAIM_PLUGIN_PREF_NONE); + gaim_plugin_pref_set_label(pref, "Save NAB group members locally"); + gaim_plugin_pref_frame_add(frame, pref); + +#if 0 + pref = gaim_plugin_pref_new_with_label("Credits"); + gaim_plugin_pref_frame_add(frame, pref); + + msg = ( PLUGIN_NAME " - " PLUGIN_DESC "\n" + "Version " VERSION "\n" + PLUGIN_AUTHOR "\n" + PLUGIN_HOMEPAGE ); + + pref = gaim_plugin_pref_new(); + gaim_plugin_pref_set_type(pref, GAIM_PLUGIN_PREF_INFO); + gaim_plugin_pref_set_label(pref, msg); + gaim_plugin_pref_frame_add(frame, pref); +#endif + + return frame; +} + + +static GaimPluginUiInfo mw_plugin_ui_info = { + .get_plugin_pref_frame = mw_plugin_get_plugin_pref_frame, +}; + + +static void status_msg_action_cb(GaimConnection *gc, + GaimRequestFields *fields) { + GaimAccount *acct; + GaimRequestField *f; + const char *msg; + /* gboolean prompt; */ + + struct mwGaimPluginData *pd; + struct mwServiceStorage *srvc; + struct mwStorageUnit *unit; + + pd = gc->proto_data; + srvc = pd->srvc_store; + + acct = gaim_connection_get_account(gc); + + f = gaim_request_fields_get_field(fields, "active"); + msg = gaim_request_field_string_get_value(f); + gaim_account_set_string(acct, MW_KEY_ACTIVE_MSG, msg); + unit = mwStorageUnit_newString(mwStore_ACTIVE_MESSAGES, msg); + mwServiceStorage_save(srvc, unit, NULL, NULL, NULL); + + f = gaim_request_fields_get_field(fields, "away"); + msg = gaim_request_field_string_get_value(f); + gaim_account_set_string(acct, MW_KEY_AWAY_MSG, msg); + unit = mwStorageUnit_newString(mwStore_AWAY_MESSAGES, msg); + mwServiceStorage_save(srvc, unit, NULL, NULL, NULL); + + f = gaim_request_fields_get_field(fields, "busy"); + msg = gaim_request_field_string_get_value(f); + gaim_account_set_string(acct, MW_KEY_BUSY_MSG, msg); + unit = mwStorageUnit_newString(mwStore_BUSY_MESSAGES, msg); + mwServiceStorage_save(srvc, unit, NULL, NULL, NULL); + +#if 0 + /** @todo not yet used. It should be possible to prompt the user for + a message (ala the Sametime Connect client) when changing to one + of the default states, and that preference is here */ + f = gaim_request_fields_get_field(fields, "prompt"); + prompt = gaim_request_field_bool_get_value(f); + gaim_account_set_bool(acct, MW_KEY_MSG_PROMPT, prompt); +#endif + +#if 0 + /* XXX */ + /* need to propagate the message change if we're in any of those + default states */ + msg = NULL; + if(!gc->away_state || !strcmp(gc->away_state, MW_STATE_ACTIVE)) { + msg = MW_STATE_ACTIVE; + } else if(gc->away_state && !strcmp(gc->away_state, MW_STATE_AWAY)) { + msg = MW_STATE_AWAY; + } else if(gc->away_state && !strcmp(gc->away_state, MW_STATE_BUSY)) { + msg = MW_STATE_BUSY; + } + if(msg) + serv_set_away(gc, msg, NULL); +#endif +} + + +/** Prompt for messages for the three default status types. These + values should be mirrored as strings in the storage service */ +static void status_msg_action(GaimPluginAction *act) { + GaimConnection *gc; + GaimAccount *acct; + + GaimRequestFields *fields; + GaimRequestFieldGroup *g; + GaimRequestField *f; + + char *msgA, *msgB; + const char *val; + /* gboolean prompt; */ + + gc = act->context; + acct = gaim_connection_get_account(gc); + + fields = gaim_request_fields_new(); + + g = gaim_request_field_group_new(NULL); + gaim_request_fields_add_group(fields, g); + + val = gaim_account_get_string(acct, MW_KEY_ACTIVE_MSG, + MW_PLUGIN_DEFAULT_ACTIVE_MSG); + f = gaim_request_field_string_new("active", "Active Message", val, FALSE); + gaim_request_field_set_required(f, FALSE); + gaim_request_field_group_add_field(g, f); + + val = gaim_account_get_string(acct, MW_KEY_AWAY_MSG, + MW_PLUGIN_DEFAULT_AWAY_MSG); + f = gaim_request_field_string_new("away", "Away Message", val, FALSE); + gaim_request_field_set_required(f, FALSE); + gaim_request_field_group_add_field(g, f); + + val = gaim_account_get_string(acct, MW_KEY_BUSY_MSG, + MW_PLUGIN_DEFAULT_BUSY_MSG); + f = gaim_request_field_string_new("busy", "Busy Message", val, FALSE); + gaim_request_field_set_required(f, FALSE); + gaim_request_field_group_add_field(g, f); + +#if 0 + /** @todo not yet used. It should be possible to prompt the user for + a message (ala the Sametime Connect client) when changing to one + of the default states, and that preference is here */ + prompt = gaim_account_get_bool(acct, MW_KEY_MSG_PROMPT, FALSE); + f = gaim_request_field_bool_new("prompt", + ("Prompt for message when changing" + " to one of these states?"), FALSE); + gaim_request_field_group_add_field(g, f); +#endif + + msgA = ("Default status messages"); + msgB = (""); + + gaim_request_fields(gc, "Edit Status Messages", + msgA, msgB, fields, + _("OK"), G_CALLBACK(status_msg_action_cb), + _("Cancel"), NULL, + gc); +} + + +static void st_import_action_cb(GaimConnection *gc, char *filename) { + struct mwSametimeList *l; + + FILE *file; + char buf[BUF_LEN]; + size_t len; + + GString *str; + + file = fopen(filename, "r"); + g_return_if_fail(file != NULL); + + str = g_string_new(NULL); + while( (len = fread(buf, 1, BUF_LEN, file)) ) { + g_string_append_len(str, buf, len); + } + + fclose(file); + + l = mwSametimeList_load(str->str); + g_string_free(str, TRUE); + + blist_import(gc, l); + mwSametimeList_free(l); +} + + +/** prompts for a file to import blist from */ +static void st_import_action(GaimPluginAction *act) { + GaimConnection *gc; + GaimAccount *account; + char *title; + + gc = act->context; + account = gaim_connection_get_account(gc); + title = g_strdup_printf("Import Sametime List for Account %s", + gaim_account_get_username(account)); + + gaim_request_file(gc, title, NULL, FALSE, + G_CALLBACK(st_import_action_cb), NULL, + gc); + + g_free(title); +} + + +static void st_export_action_cb(GaimConnection *gc, char *filename) { + struct mwSametimeList *l; + char *str; + FILE *file; + + file = fopen(filename, "w"); + g_return_if_fail(file != NULL); + + l = mwSametimeList_new(); + blist_export(gc, l); + str = mwSametimeList_store(l); + mwSametimeList_free(l); + + fprintf(file, "%s", str); + fclose(file); + + g_free(str); +} + + +/** prompts for a file to export blist to */ +static void st_export_action(GaimPluginAction *act) { + GaimConnection *gc; + GaimAccount *account; + char *title; + + gc = act->context; + account = gaim_connection_get_account(gc); + title = g_strdup_printf("Export Sametime List for Account %s", + gaim_account_get_username(account)); + + gaim_request_file(gc, title, NULL, TRUE, + G_CALLBACK(st_export_action_cb), NULL, + gc); + + g_free(title); +} + + +static void remote_group_multi_cleanup(gpointer ignore, + GaimRequestFields *fields) { + + GaimRequestField *f; + const GList *l; + + f = gaim_request_fields_get_field(fields, "group"); + l = gaim_request_field_list_get_items(f); + + for(; l; l = l->next) { + const char *i = l->data; + struct resolved_id *res; + + res = gaim_request_field_list_get_data(f, i); + + g_free(res->id); + g_free(res->name); + g_free(res); + } +} + + +static void remote_group_done(struct mwGaimPluginData *pd, + const char *id, const char *name) { + GaimConnection *gc; + GaimAccount *acct; + GaimGroup *group; + GaimBlistNode *gn; + const char *owner; + + g_return_if_fail(pd != NULL); + + gc = pd->gc; + acct = gaim_connection_get_account(gc); + + /* collision checking */ + group = gaim_find_group(name); + if(group) { + char *msgA, *msgB; + + msgA = "Unable to add group: group exists"; + msgB = "A group named '%s' already exists in your buddy list."; + msgB = g_strdup_printf(msgB, name); + + gaim_notify_error(gc, "Unable to add group", msgA, msgB); + + g_free(msgB); + return; + } + + group = gaim_group_new(name); + gn = (GaimBlistNode *) group; + + owner = gaim_account_get_username(acct); + + gaim_blist_node_set_string(gn, GROUP_KEY_NAME, id); + gaim_blist_node_set_int(gn, GROUP_KEY_TYPE, mwSametimeGroup_DYNAMIC); + gaim_blist_node_set_string(gn, GROUP_KEY_OWNER, owner); + gaim_blist_add_group(group, NULL); + + group_add(pd, group); + blist_schedule(pd); +} + + +static void remote_group_multi_cb(struct mwGaimPluginData *pd, + GaimRequestFields *fields) { + GaimRequestField *f; + const GList *l; + + f = gaim_request_fields_get_field(fields, "group"); + l = gaim_request_field_list_get_selected(f); + + if(l) { + const char *i = l->data; + struct resolved_id *res; + + res = gaim_request_field_list_get_data(f, i); + remote_group_done(pd, res->id, res->name); + } + + remote_group_multi_cleanup(NULL, fields); +} + + +static void remote_group_multi(struct mwResolveResult *result, + struct mwGaimPluginData *pd) { + + GaimRequestFields *fields; + GaimRequestFieldGroup *g; + GaimRequestField *f; + GList *l; + char *msgA, *msgB; + + GaimConnection *gc = pd->gc; + + fields = gaim_request_fields_new(); + + g = gaim_request_field_group_new(NULL); + gaim_request_fields_add_group(fields, g); + + f = gaim_request_field_list_new("group", "Possible Matches"); + gaim_request_field_list_set_multi_select(f, FALSE); + gaim_request_field_set_required(f, TRUE); + + for(l = result->matches; l; l = l->next) { + struct mwResolveMatch *match = l->data; + struct resolved_id *res = g_new0(struct resolved_id, 1); + + res->id = g_strdup(match->id); + res->name = g_strdup(match->name); + + gaim_request_field_list_add(f, res->name, res); + } + + gaim_request_field_group_add_field(g, f); + + msgA = ("Notes Address Book group results"); + msgB = ("The identifier '%s' may possibly refer to any of the following" + " Notes Address Book groups. Please select the correct group from" + " the list below to add it to your buddy list."); + msgB = g_strdup_printf(msgB, result->name); + + gaim_request_fields(gc, "Select Notes Address Book", + msgA, msgB, fields, + "Add Group", G_CALLBACK(remote_group_multi_cb), + "Cancel", G_CALLBACK(remote_group_multi_cleanup), + pd); + + g_free(msgB); +} + + +static void remote_group_resolved(struct mwServiceResolve *srvc, + guint32 id, guint32 code, GList *results, + gpointer b) { + struct mwSession *session; + struct mwGaimPluginData *pd; + GaimConnection *gc; + + struct mwResolveResult *res = NULL; + + session = mwService_getSession(MW_SERVICE(srvc)); + g_return_if_fail(session != NULL); + + pd = mwSession_getClientData(session); + g_return_if_fail(pd != NULL); + + gc = pd->gc; + g_return_if_fail(gc != NULL); + + if(!code && results) { + res = results->data; + + if(res->matches) { + remote_group_multi(res, pd); + return; + } + } + + if(res && res->name) { + char *msgA, *msgB; + + msgA = "Unable to add group: group not found"; + + msgB = ("The identifier '%s' did not match any Notes Address Book" + " groups in your Sametime community."); + msgB = g_strdup_printf(msgB, res->name); + + gaim_notify_error(gc, "Unable to add group", msgA, msgB); + + g_free(msgB); + } +} + + +static void remote_group_action_cb(GaimConnection *gc, const char *name) { + struct mwGaimPluginData *pd; + struct mwServiceResolve *srvc; + GList *query; + enum mwResolveFlag flags; + guint32 req; + + pd = gc->proto_data; + srvc = pd->srvc_resolve; + + query = g_list_prepend(NULL, (char *) name); + flags = mwResolveFlag_FIRST | mwResolveFlag_GROUPS; + + req = mwServiceResolve_resolve(srvc, query, flags, remote_group_resolved, + NULL, NULL); + g_list_free(query); + + if(req == SEARCH_ERROR) { + /** @todo display error */ + } +} + + +static void remote_group_action(GaimPluginAction *act) { + GaimConnection *gc; + const char *msgA, *msgB; + + gc = act->context; + + msgA = "Notes Address Book Group"; + msgB = ("Enter the name of a Notes Address Book group in the field below" + " to add the group and its members to your buddy list."); + + gaim_request_input(gc, "Add Group", msgA, msgB, NULL, + FALSE, FALSE, NULL, + "Add", G_CALLBACK(remote_group_action_cb), + "Cancel", NULL, + gc); +} + + +static GList *mw_plugin_actions(GaimPlugin *plugin, gpointer context) { + GaimPluginAction *act; + GList *l = NULL; + + act = gaim_plugin_action_new("Set Status Messages...", status_msg_action); + l = g_list_append(l, act); + + act = gaim_plugin_action_new("Import Sametime List...", st_import_action); + l = g_list_append(l, act); + + act = gaim_plugin_action_new("Export Sametime List...", st_export_action); + l = g_list_append(l, act); + + act = gaim_plugin_action_new("Add Notes Address Book Group...", + remote_group_action); + l = g_list_append(l, act); + + return l; +} + + +static gboolean mw_plugin_load(GaimPlugin *plugin) { + return TRUE; +} + + +static gboolean mw_plugin_unload(GaimPlugin *plugin) { + return TRUE; +} + + +static void mw_plugin_destroy(GaimPlugin *plugin) { + g_log_remove_handler(G_LOG_DOMAIN, log_handler[0]); + g_log_remove_handler("meanwhile", log_handler[1]); +} + + +static GaimPluginInfo mw_plugin_info = { + .magic = GAIM_PLUGIN_MAGIC, + .major_version = GAIM_MAJOR_VERSION, + .minor_version = GAIM_MINOR_VERSION, + .type = GAIM_PLUGIN_PROTOCOL, + .ui_requirement = NULL, + .flags = 0, + .dependencies = NULL, + .priority = GAIM_PRIORITY_DEFAULT, + .id = PLUGIN_ID, + .name = PLUGIN_NAME, + .version = VERSION, + .summary = PLUGIN_SUMMARY, + .description = PLUGIN_DESC, + .author = PLUGIN_AUTHOR, + .homepage = PLUGIN_HOMEPAGE, + .load = mw_plugin_load, + .unload = mw_plugin_unload, + .destroy = mw_plugin_destroy, + .ui_info = NULL, + .extra_info = &mw_prpl_info, + .prefs_info = &mw_plugin_ui_info, + .actions = mw_plugin_actions, +}; + + +static void mw_log_handler(const gchar *domain, GLogLevelFlags flags, + const gchar *msg, gpointer data) { + char *nl; + + if(! msg) return; + + /* annoying! */ + nl = g_strdup_printf("%s\n", msg); + + /* handle g_log requests via gaim's built-in debug logging */ + if(flags & G_LOG_LEVEL_ERROR) { + gaim_debug_error(domain, nl); + + } else if(flags & G_LOG_LEVEL_WARNING) { + gaim_debug_warning(domain, nl); + + } else { + gaim_debug_info(domain, nl); + } + + g_free(nl); +} + + +static void mw_plugin_init(GaimPlugin *plugin) { + GaimAccountOption *opt; + GList *l = NULL; + + GLogLevelFlags logflags = + G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION; + + /* host to connect to */ + opt = gaim_account_option_string_new("Server", MW_KEY_HOST, + MW_PLUGIN_DEFAULT_HOST); + l = g_list_append(l, opt); + + /* port to connect to */ + opt = gaim_account_option_int_new("Port", MW_KEY_PORT, + MW_PLUGIN_DEFAULT_PORT); + l = g_list_append(l, opt); + + mw_prpl_info.protocol_options = l; + l = NULL; + + /* set up the prefs for blist options */ + gaim_prefs_add_none(MW_PRPL_OPT_BASE); + gaim_prefs_add_int(MW_PRPL_OPT_BLIST_ACTION, BLIST_CHOICE_DEFAULT); + gaim_prefs_add_bool(MW_PRPL_OPT_PSYCHIC, FALSE); + gaim_prefs_add_bool(MW_PRPL_OPT_FORCE_LOGIN, FALSE); + gaim_prefs_add_bool(MW_PRPL_OPT_SAVE_DYNAMIC, TRUE); + + /* forward all our g_log messages to gaim. Generally all the logging + calls are using gaim_log directly, but the g_return macros will + get caught here */ + log_handler[0] = g_log_set_handler(G_LOG_DOMAIN, logflags, + mw_log_handler, NULL); + + /* redirect meanwhile's logging to gaim's */ + log_handler[1] = g_log_set_handler("meanwhile", logflags, + mw_log_handler, NULL); +} + + +GAIM_INIT_PLUGIN(meanwhile, mw_plugin_init, mw_plugin_info); +/* The End. */ + diff -r be22eb8fa671 -r 2ce8ec01a064 src/protocols/sametime/sametime.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/sametime/sametime.h Tue Jun 07 02:52:39 2005 +0000 @@ -0,0 +1,50 @@ + + +/* CFLAGS trumps configure values */ + + +/** default active message */ +#ifndef MW_PLUGIN_DEFAULT_ACTIVE_MSG +#define MW_PLUGIN_DEFAULT_ACTIVE_MSG "Talk to me" +#endif +/* "Talk to me" */ + + +/** default away message */ +#ifndef MW_PLUGIN_DEFAULT_AWAY_MSG +#define MW_PLUGIN_DEFAULT_AWAY_MSG "Not here right now" +#endif +/* "Not here right now" */ + + +/** default busy message */ +#ifndef MW_PLUGIN_DEFAULT_BUSY_MSG +#define MW_PLUGIN_DEFAULT_BUSY_MSG "Please do not disturb me" +#endif +/* "Please do not disturb me" */ + + +/** default host for the gaim plugin. You can specialize a build to + default to your server by supplying this at compile time */ +#ifndef MW_PLUGIN_DEFAULT_HOST +#define MW_PLUGIN_DEFAULT_HOST "" +#endif +/* "" */ + + +/** default port for the gaim plugin. You can specialize a build to + default to your server by supplying this at compile time */ +#ifndef MW_PLUGIN_DEFAULT_PORT +#define MW_PLUGIN_DEFAULT_PORT 1533 +#endif +/* 1533 */ + + +/** client id to report to the server. See mwLoginType in meanwhile's + mw_common.h for some sample values */ +#ifndef MW_CLIENT_TYPE_ID +#define MW_CLIENT_TYPE_ID mwLogin_MEANWHILE +#endif +/* mwLogin_MEANWHILE */ + +