/*
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 <glib.h>
#include <glib/glist.h>
#include <string.h>
#include "mw_channel.h"
#include "mw_debug.h"
#include "mw_error.h"
#include "mw_message.h"
#include "mw_service.h"
#include "mw_session.h"
#include "mw_srvc_im.h"
#include "mw_util.h"
#define PROTOCOL_TYPE 0x00001000
#define PROTOCOL_VER 0x00000003
/* data for the addtl blocks of channel creation */
#define mwImAddtlA_NORMAL 0x00000001
#define mwImAddtlB_NORMAL 0x00000001 /**< standard */
#define mwImAddtlB_PRECONF 0x00000019 /**< pre-conference chat */
#define mwImAddtlB_NOTESBUDDY 0x00033453 /**< notesbuddy */
#define mwImAddtlC_NORMAL 0x00000002
/* send-on-channel message type */
#define msg_MESSAGE 0x0064 /**< IM message */
#define BREAKUP 2048
/* which type of im? */
enum mwImType {
mwIm_TEXT = 0x00000001, /**< text message */
mwIm_DATA = 0x00000002, /**< status message (usually) */
};
/* which type of data im? */
enum mwImDataType {
mwImData_TYPING = 0x00000001, /**< common use typing indicator */
mwImData_SUBJECT = 0x00000003, /**< notesbuddy IM topic */
mwImData_HTML = 0x00000004, /**< notesbuddy HTML message */
mwImData_MIME = 0x00000005, /**< notesbuddy MIME message, w/image */
mwImData_TIMESTAMP = 0x00000006, /**< notesbuddy timestamp */
mwImData_INVITE = 0x0000000a, /**< Places invitation */
mwImData_MULTI_START = 0x00001388,
mwImData_MULTI_STOP = 0x00001389,
};
/** @todo might be appropriate to make a couple of hashtables to
reference conversations by channel and target */
struct mwServiceIm {
struct mwService service;
enum mwImClientType features;
struct mwImHandler *handler;
GList *convs; /**< list of struct im_convo */
};
struct mwConversation {
struct mwServiceIm *service; /**< owning service */
struct mwChannel *channel; /**< channel */
struct mwIdBlock target; /**< conversation target */
gboolean ext_id; /**< special treatment, external ID */
/** state of the conversation, based loosely on the state of its
underlying channel */
enum mwConversationState state;
enum mwImClientType features;
GString *multi; /**< buffer for multi-chunk message */
enum mwImSendType multi_type; /**< type of incoming multi-chunk message */
struct mw_datum client_data;
};
/** momentarily places a mwLoginInfo into a mwIdBlock */
static void login_into_id(struct mwIdBlock *to, struct mwLoginInfo *from) {
to->user = from->user_id;
to->community = from->community;
}
static struct mwConversation *convo_find_by_user(struct mwServiceIm *srvc,
struct mwIdBlock *to) {
GList *l;
for(l = srvc->convs; l; l = l->next) {
struct mwConversation *c = l->data;
if(mwIdBlock_equal(&c->target, to))
return c;
}
return NULL;
}
static const char *conv_state_str(enum mwConversationState state) {
switch(state) {
case mwConversation_CLOSED:
return "closed";
case mwConversation_OPEN:
return "open";
case mwConversation_PENDING:
return "pending";
case mwConversation_UNKNOWN:
default:
return "UNKNOWN";
}
}
static void convo_set_state(struct mwConversation *conv,
enum mwConversationState state) {
g_return_if_fail(conv != NULL);
if(conv->state != state) {
g_info("setting conversation (%s, %s) state: %s",
NSTR(conv->target.user), NSTR(conv->target.community),
conv_state_str(state));
conv->state = state;
}
}
struct mwConversation *mwServiceIm_findConversation(struct mwServiceIm *srvc,
struct mwIdBlock *to) {
g_return_val_if_fail(srvc != NULL, NULL);
g_return_val_if_fail(to != NULL, NULL);
return convo_find_by_user(srvc, to);
}
struct mwConversation *mwServiceIm_getConversation(struct mwServiceIm *srvc,
struct mwIdBlock *to) {
struct mwConversation *c;
g_return_val_if_fail(srvc != NULL, NULL);
g_return_val_if_fail(to != NULL, NULL);
c = convo_find_by_user(srvc, to);
if(! c) {
c = g_new0(struct mwConversation, 1);
c->service = srvc;
mwIdBlock_clone(&c->target, to);
c->state = mwConversation_CLOSED;
c->features = srvc->features;
/* mark external users */
/* c->ext_id = g_str_has_prefix(to->user, "@E "); */
srvc->convs = g_list_prepend(srvc->convs, c);
}
return c;
}
static void convo_create_chan(struct mwConversation *c) {
struct mwSession *s;
struct mwChannelSet *cs;
struct mwChannel *chan;
struct mwLoginInfo *login;
struct mwPutBuffer *b;
/* we only should be calling this if there isn't a channel already
associated with the conversation */
g_return_if_fail(c != NULL);
g_return_if_fail(mwConversation_isPending(c));
g_return_if_fail(c->channel == NULL);
s = mwService_getSession(MW_SERVICE(c->service));
cs = mwSession_getChannels(s);
chan = mwChannel_newOutgoing(cs);
mwChannel_setService(chan, MW_SERVICE(c->service));
mwChannel_setProtoType(chan, PROTOCOL_TYPE);
mwChannel_setProtoVer(chan, PROTOCOL_VER);
/* offer all known ciphers */
mwChannel_populateSupportedCipherInstances(chan);
/* set the target */
login = mwChannel_getUser(chan);
login->user_id = g_strdup(c->target.user);
login->community = g_strdup(c->target.community);
/* compose the addtl create, with optional FANCY HTML! */
b = mwPutBuffer_new();
guint32_put(b, mwImAddtlA_NORMAL);
guint32_put(b, c->features);
mwPutBuffer_finalize(mwChannel_getAddtlCreate(chan), b);
c->channel = mwChannel_create(chan)? NULL: chan;
if(c->channel) {
mwChannel_setServiceData(c->channel, c, NULL);
}
}
void mwConversation_open(struct mwConversation *conv) {
g_return_if_fail(conv != NULL);
g_return_if_fail(mwConversation_isClosed(conv));
convo_set_state(conv, mwConversation_PENDING);
convo_create_chan(conv);
}
static void convo_opened(struct mwConversation *conv) {
struct mwImHandler *h = NULL;
g_return_if_fail(conv != NULL);
g_return_if_fail(conv->service != NULL);
convo_set_state(conv, mwConversation_OPEN);
h = conv->service->handler;
g_return_if_fail(h != NULL);
if(h->conversation_opened)
h->conversation_opened(conv);
}
static void convo_free(struct mwConversation *conv) {
struct mwServiceIm *srvc;
mwConversation_removeClientData(conv);
srvc = conv->service;
srvc->convs = g_list_remove_all(srvc->convs, conv);
mwIdBlock_clear(&conv->target);
g_free(conv);
}
static int send_accept(struct mwConversation *c) {
struct mwChannel *chan = c->channel;
struct mwSession *s = mwChannel_getSession(chan);
struct mwUserStatus *stat = mwSession_getUserStatus(s);
struct mwPutBuffer *b;
struct mwOpaque *o;
b = mwPutBuffer_new();
guint32_put(b, mwImAddtlA_NORMAL);
guint32_put(b, c->features);
guint32_put(b, mwImAddtlC_NORMAL);
mwUserStatus_put(b, stat);
o = mwChannel_getAddtlAccept(chan);
mwOpaque_clear(o);
mwPutBuffer_finalize(o, b);
return mwChannel_accept(chan);
}
static void recv_channelCreate(struct mwService *srvc,
struct mwChannel *chan,
struct mwMsgChannelCreate *msg) {
/* - ensure it's the right service,proto,and proto ver
- check the opaque for the right opaque junk
- if not, close channel
- compose & send a channel accept
*/
struct mwServiceIm *srvc_im = (struct mwServiceIm *) srvc;
struct mwImHandler *handler;
struct mwSession *s;
struct mwUserStatus *stat;
guint32 x, y, z;
struct mwGetBuffer *b;
struct mwConversation *c = NULL;
struct mwIdBlock idb;
handler = srvc_im->handler;
s = mwChannel_getSession(chan);
stat = mwSession_getUserStatus(s);
/* ensure the appropriate service/proto/ver */
x = mwChannel_getServiceId(chan);
y = mwChannel_getProtoType(chan);
z = mwChannel_getProtoVer(chan);
if( (x != mwService_IM) || (y != PROTOCOL_TYPE) || (z != PROTOCOL_VER) ) {
g_warning("unacceptable service, proto, ver:"
" 0x%08x, 0x%08x, 0x%08x", x, y, z);
mwChannel_destroy(chan, ERR_SERVICE_NO_SUPPORT, NULL);
return;
}
/* act upon the values in the addtl block */
b = mwGetBuffer_wrap(&msg->addtl);
guint32_get(b, &x);
guint32_get(b, &y);
z = (guint) mwGetBuffer_error(b);
mwGetBuffer_free(b);
if(z /* buffer error, BOOM! */ ) {
g_warning("bad/malformed addtl in IM service");
mwChannel_destroy(chan, ERR_FAILURE, NULL);
return;
} else if(x != mwImAddtlA_NORMAL) {
g_message("unknown params: 0x%08x, 0x%08x", x, y);
mwChannel_destroy(chan, ERR_IM_NOT_REGISTERED, NULL);
return;
} else if(y == mwImAddtlB_PRECONF) {
if(! handler->place_invite) {
g_info("rejecting place-invite channel");
mwChannel_destroy(chan, ERR_IM_NOT_REGISTERED, NULL);
return;
} else {
g_info("accepting place-invite channel");
}
} else if(y != mwImClient_PLAIN && y != srvc_im->features) {
/** reject what we do not understand */
mwChannel_destroy(chan, ERR_IM_NOT_REGISTERED, NULL);
return;
} else if(stat->status == mwStatus_BUSY) {
g_info("rejecting IM channel due to DND status");
mwChannel_destroy(chan, ERR_CLIENT_USER_DND, NULL);
return;
}
login_into_id(&idb, mwChannel_getUser(chan));
#if 0
c = convo_find_by_user(srvc_im, &idb);
#endif
if(! c) {
c = g_new0(struct mwConversation, 1);
c->service = srvc_im;
srvc_im->convs = g_list_prepend(srvc_im->convs, c);
}
#if 0
/* we're going to re-associate any existing conversations with this
new channel. That means closing any existing ones */
if(c->channel) {
g_info("closing existing IM channel 0x%08x", mwChannel_getId(c->channel));
mwConversation_close(c, ERR_SUCCESS);
}
#endif
/* set up the conversation with this channel, target, and be fancy
if the other side requested it */
c->channel = chan;
mwIdBlock_clone(&c->target, &idb);
c->features = y;
convo_set_state(c, mwConversation_PENDING);
mwChannel_setServiceData(c->channel, c, NULL);
if(send_accept(c)) {
g_warning("sending IM channel accept failed");
mwConversation_free(c);
} else {
convo_opened(c);
}
}
static void recv_channelAccept(struct mwService *srvc, struct mwChannel *chan,
struct mwMsgChannelAccept *msg) {
struct mwConversation *conv;
conv = mwChannel_getServiceData(chan);
if(! conv) {
g_warning("received channel accept for non-existant conversation");
mwChannel_destroy(chan, ERR_FAILURE, NULL);
return;
}
convo_opened(conv);
}
static void recv_channelDestroy(struct mwService *srvc, struct mwChannel *chan,
struct mwMsgChannelDestroy *msg) {
struct mwConversation *c;
c = mwChannel_getServiceData(chan);
g_return_if_fail(c != NULL);
c->channel = NULL;
if(mwChannel_isState(chan, mwChannel_ERROR)) {
/* checking for failure on the receiving end to accept html
messages. Fail-over to a non-html format on a new channel for
the convo */
if(c->features != mwImClient_PLAIN
&& (msg->reason == ERR_IM_NOT_REGISTERED ||
msg->reason == ERR_SERVICE_NO_SUPPORT)) {
g_debug("falling back on a plaintext conversation");
c->features = mwImClient_PLAIN;
convo_create_chan(c);
return;
}
}
mwConversation_close(c, msg->reason);
}
static void convo_recv(struct mwConversation *conv, enum mwImSendType type,
gconstpointer msg) {
struct mwServiceIm *srvc;
struct mwImHandler *handler;
g_return_if_fail(conv != NULL);
srvc = conv->service;
g_return_if_fail(srvc != NULL);
handler = srvc->handler;
if(handler && handler->conversation_recv)
handler->conversation_recv(conv, type, msg);
}
static void convo_multi_start(struct mwConversation *conv) {
g_return_if_fail(conv->multi == NULL);
conv->multi = g_string_new(NULL);
}
static void convo_multi_stop(struct mwConversation *conv) {
g_return_if_fail(conv->multi != NULL);
/* actually send it */
convo_recv(conv, conv->multi_type, conv->multi->str);
/* clear up the multi buffer */
g_string_free(conv->multi, TRUE);
conv->multi = NULL;
}
static void recv_text(struct mwServiceIm *srvc, struct mwChannel *chan,
struct mwGetBuffer *b) {
struct mwConversation *c;
char *text = NULL;
mwString_get(b, &text);
if(! text) return;
c = mwChannel_getServiceData(chan);
if(c) {
if(c->multi) {
g_string_append(c->multi, text);
} else {
convo_recv(c, mwImSend_PLAIN, text);
}
}
g_free(text);
}
static void convo_invite(struct mwConversation *conv,
struct mwOpaque *o) {
struct mwServiceIm *srvc;
struct mwImHandler *handler;
struct mwGetBuffer *b;
char *title, *name, *msg;
char *unkn, *host;
guint16 with_who;
g_info("convo_invite");
srvc = conv->service;
handler = srvc->handler;
g_return_if_fail(handler != NULL);
g_return_if_fail(handler->place_invite != NULL);
b = mwGetBuffer_wrap(o);
mwGetBuffer_advance(b, 4);
mwString_get(b, &title);
mwString_get(b, &msg);
mwGetBuffer_advance(b, 19);
mwString_get(b, &name);
/* todo: add a mwString_skip */
mwString_get(b, &unkn);
mwString_get(b, &host);
g_free(unkn);
g_free(host);
/* hack. Sometimes incoming convo invitation channels won't have the
owner id block filled in */
guint16_get(b, &with_who);
if(with_who && !conv->target.user) {
char *login;
mwString_get(b, &conv->target.user);
mwString_get(b, &login); g_free(login);
mwString_get(b, &conv->target.community);
}
if(mwGetBuffer_error(b)) {
mw_mailme_opaque(o, "problem with place invite over IM service");
} else {
handler->place_invite(conv, msg, title, name);
}
mwGetBuffer_free(b);
g_free(msg);
g_free(title);
g_free(name);
}
static void recv_data(struct mwServiceIm *srvc, struct mwChannel *chan,
struct mwGetBuffer *b) {
struct mwConversation *conv;
guint32 type, subtype;
struct mwOpaque o = { 0, 0 };
char *x;
guint32_get(b, &type);
guint32_get(b, &subtype);
mwOpaque_get(b, &o);
if(mwGetBuffer_error(b)) {
mwOpaque_clear(&o);
return;
}
conv = mwChannel_getServiceData(chan);
if(! conv) return;
switch(type) {
case mwImData_TYPING:
convo_recv(conv, mwImSend_TYPING, GINT_TO_POINTER(! subtype));
break;
case mwImData_HTML:
if(o.len) {
if(conv->multi) {
g_string_append_len(conv->multi, (char *) o.data, o.len);
conv->multi_type = mwImSend_HTML;
} else {
x = g_strndup((char *) o.data, o.len);
convo_recv(conv, mwImSend_HTML, x);
g_free(x);
}
}
break;
case mwImData_SUBJECT:
x = g_strndup((char *) o.data, o.len);
convo_recv(conv, mwImSend_SUBJECT, x);
g_free(x);
break;
case mwImData_MIME:
if(conv->multi) {
g_string_append_len(conv->multi, (char *) o.data, o.len);
conv->multi_type = mwImSend_MIME;
} else {
x = g_strndup((char *) o.data, o.len);
convo_recv(conv, mwImSend_MIME, x);
g_free(x);
}
break;
case mwImData_TIMESTAMP:
/* todo */
break;
case mwImData_INVITE:
convo_invite(conv, &o);
break;
case mwImData_MULTI_START:
convo_multi_start(conv);
break;
case mwImData_MULTI_STOP:
convo_multi_stop(conv);
break;
default:
mw_mailme_opaque(&o, "unknown data message type in IM service:"
" (0x%08x, 0x%08x)", type, subtype);
}
mwOpaque_clear(&o);
}
static void recv(struct mwService *srvc, struct mwChannel *chan,
guint16 type, struct mwOpaque *data) {
/* - ensure message type is something we want
- parse message type into either mwIMText or mwIMData
- handle
*/
struct mwGetBuffer *b;
guint32 mt;
g_return_if_fail(type == msg_MESSAGE);
b = mwGetBuffer_wrap(data);
guint32_get(b, &mt);
if(mwGetBuffer_error(b)) {
g_warning("failed to parse message for IM service");
mwGetBuffer_free(b);
return;
}
switch(mt) {
case mwIm_TEXT:
recv_text((struct mwServiceIm *) srvc, chan, b);
break;
case mwIm_DATA:
recv_data((struct mwServiceIm *) srvc, chan, b);
break;
default:
g_warning("unknown message type 0x%08x for IM service", mt);
}
if(mwGetBuffer_error(b))
g_warning("failed to parse message type 0x%08x for IM service", mt);
mwGetBuffer_free(b);
}
static void clear(struct mwServiceIm *srvc) {
struct mwImHandler *h;
while(srvc->convs)
convo_free(srvc->convs->data);
h = srvc->handler;
if(h && h->clear)
h->clear(srvc);
srvc->handler = NULL;
}
static const char *name(struct mwService *srvc) {
return "Instant Messaging";
}
static const char *desc(struct mwService *srvc) {
return "IM service with Standard and NotesBuddy features";
}
static void start(struct mwService *srvc) {
mwService_started(srvc);
}
static void stop(struct mwServiceIm *srvc) {
while(srvc->convs)
mwConversation_free(srvc->convs->data);
mwService_stopped(MW_SERVICE(srvc));
}
struct mwServiceIm *mwServiceIm_new(struct mwSession *session,
struct mwImHandler *hndl) {
struct mwServiceIm *srvc_im;
struct mwService *srvc;
g_return_val_if_fail(session != NULL, NULL);
g_return_val_if_fail(hndl != NULL, NULL);
srvc_im = g_new0(struct mwServiceIm, 1);
srvc = MW_SERVICE(srvc_im);
mwService_init(srvc, session, mwService_IM);
srvc->recv_create = recv_channelCreate;
srvc->recv_accept = recv_channelAccept;
srvc->recv_destroy = recv_channelDestroy;
srvc->recv = recv;
srvc->clear = (mwService_funcClear) clear;
srvc->get_name = name;
srvc->get_desc = desc;
srvc->start = start;
srvc->stop = (mwService_funcStop) stop;
srvc_im->features = mwImClient_PLAIN;
srvc_im->handler = hndl;
return srvc_im;
}
struct mwImHandler *mwServiceIm_getHandler(struct mwServiceIm *srvc) {
g_return_val_if_fail(srvc != NULL, NULL);
return srvc->handler;
}
gboolean mwServiceIm_supports(struct mwServiceIm *srvc,
enum mwImSendType type) {
g_return_val_if_fail(srvc != NULL, FALSE);
switch(type) {
case mwImSend_PLAIN:
case mwImSend_TYPING:
return TRUE;
case mwImSend_SUBJECT:
case mwImSend_HTML:
case mwImSend_MIME:
case mwImSend_TIMESTAMP:
return srvc->features == mwImClient_NOTESBUDDY;
default:
return FALSE;
}
}
void mwServiceIm_setClientType(struct mwServiceIm *srvc,
enum mwImClientType type) {
g_return_if_fail(srvc != NULL);
srvc->features = type;
}
enum mwImClientType mwServiceIm_getClientType(struct mwServiceIm *srvc) {
g_return_val_if_fail(srvc != NULL, mwImClient_UNKNOWN);
return srvc->features;
}
static int convo_send_data(struct mwConversation *conv,
guint32 type, guint32 subtype,
struct mwOpaque *data) {
struct mwPutBuffer *b;
struct mwOpaque o;
struct mwChannel *chan;
int ret;
chan = conv->channel;
g_return_val_if_fail(chan != NULL, -1);
b = mwPutBuffer_new();
guint32_put(b, mwIm_DATA);
guint32_put(b, type);
guint32_put(b, subtype);
mwOpaque_put(b, data);
mwPutBuffer_finalize(&o, b);
ret = mwChannel_sendEncrypted(chan, msg_MESSAGE, &o, !conv->ext_id);
mwOpaque_clear(&o);
return ret;
}
static int convo_send_multi_start(struct mwConversation *conv) {
return convo_send_data(conv, mwImData_MULTI_START, 0x00, NULL);
}
static int convo_send_multi_stop(struct mwConversation *conv) {
return convo_send_data(conv, mwImData_MULTI_STOP, 0x00, NULL);
}
/* breaks up a large message into segments, sends a start_segment
message, then sends each segment in turn, then sends a stop_segment
message */
static int
convo_sendSegmented(struct mwConversation *conv, const char *message,
int (*send)(struct mwConversation *conv,
const char *msg)) {
char *buf = (char *) message;
gsize len;
int ret = 0;
len = strlen(buf);
ret = convo_send_multi_start(conv);
while(len && !ret) {
char tail;
gsize seg;
seg = BREAKUP;
if(len < BREAKUP)
seg = len;
/* temporarily NUL-terminate this segment */
tail = buf[seg];
buf[seg] = 0x00;
ret = send(conv, buf);
/* restore this segment */
buf[seg] = tail;
buf += seg;
len -= seg;
}
if(! ret)
ret = convo_send_multi_stop(conv);
return ret;
}
static int convo_sendText(struct mwConversation *conv, const char *text) {
struct mwPutBuffer *b;
struct mwOpaque o;
int ret;
b = mwPutBuffer_new();
guint32_put(b, mwIm_TEXT);
mwString_put(b, text);
mwPutBuffer_finalize(&o, b);
ret = mwChannel_sendEncrypted(conv->channel, msg_MESSAGE, &o, !conv->ext_id);
mwOpaque_clear(&o);
return ret;
}
static int convo_sendTyping(struct mwConversation *conv, gboolean typing) {
return convo_send_data(conv, mwImData_TYPING, !typing, NULL);
}
static int convo_sendSubject(struct mwConversation *conv,
const char *subject) {
struct mwOpaque o;
o.len = strlen(subject);
o.data = (guchar *) subject;
return convo_send_data(conv, mwImData_SUBJECT, 0x00, &o);
}
static int convo_sendHtml(struct mwConversation *conv, const char *html) {
struct mwOpaque o;
o.len = strlen(html);
o.data = (guchar *) html;
if(o.len > BREAKUP) {
return convo_sendSegmented(conv, html, convo_sendHtml);
} else {
return convo_send_data(conv, mwImData_HTML, 0x00, &o);
}
}
static int convo_sendMime(struct mwConversation *conv, const char *mime) {
struct mwOpaque o;
o.len = strlen(mime);
o.data = (guchar *) mime;
if(o.len > BREAKUP) {
return convo_sendSegmented(conv, mime, convo_sendMime);
} else {
return convo_send_data(conv, mwImData_MIME, 0x00, &o);
}
}
int mwConversation_send(struct mwConversation *conv, enum mwImSendType type,
gconstpointer msg) {
g_return_val_if_fail(conv != NULL, -1);
g_return_val_if_fail(mwConversation_isOpen(conv), -1);
g_return_val_if_fail(conv->channel != NULL, -1);
switch(type) {
case mwImSend_PLAIN:
return convo_sendText(conv, msg);
case mwImSend_TYPING:
return convo_sendTyping(conv, GPOINTER_TO_INT(msg));
case mwImSend_SUBJECT:
return convo_sendSubject(conv, msg);
case mwImSend_HTML:
return convo_sendHtml(conv, msg);
case mwImSend_MIME:
return convo_sendMime(conv, msg);
default:
g_warning("unsupported IM Send Type, 0x%x", type);
return -1;
}
}
enum mwConversationState mwConversation_getState(struct mwConversation *conv) {
g_return_val_if_fail(conv != NULL, mwConversation_UNKNOWN);
return conv->state;
}
struct mwServiceIm *mwConversation_getService(struct mwConversation *conv) {
g_return_val_if_fail(conv != NULL, NULL);
return conv->service;
}
gboolean mwConversation_supports(struct mwConversation *conv,
enum mwImSendType type) {
g_return_val_if_fail(conv != NULL, FALSE);
switch(type) {
case mwImSend_PLAIN:
case mwImSend_TYPING:
return TRUE;
case mwImSend_SUBJECT:
case mwImSend_HTML:
case mwImSend_MIME:
return conv->features == mwImClient_NOTESBUDDY;
default:
return FALSE;
}
}
enum mwImClientType
mwConversation_getClientType(struct mwConversation *conv) {
g_return_val_if_fail(conv != NULL, mwImClient_UNKNOWN);
return conv->features;
}
struct mwIdBlock *mwConversation_getTarget(struct mwConversation *conv) {
g_return_val_if_fail(conv != NULL, NULL);
return &conv->target;
}
struct mwLoginInfo *mwConversation_getTargetInfo(struct mwConversation *conv) {
g_return_val_if_fail(conv != NULL, NULL);
g_return_val_if_fail(conv->channel != NULL, NULL);
return mwChannel_getUser(conv->channel);
}
void mwConversation_setClientData(struct mwConversation *conv,
gpointer data, GDestroyNotify clean) {
g_return_if_fail(conv != NULL);
mw_datum_set(&conv->client_data, data, clean);
}
gpointer mwConversation_getClientData(struct mwConversation *conv) {
g_return_val_if_fail(conv != NULL, NULL);
return mw_datum_get(&conv->client_data);
}
void mwConversation_removeClientData(struct mwConversation *conv) {
g_return_if_fail(conv != NULL);
mw_datum_clear(&conv->client_data);
}
void mwConversation_close(struct mwConversation *conv, guint32 reason) {
struct mwServiceIm *srvc;
struct mwImHandler *h;
g_return_if_fail(conv != NULL);
convo_set_state(conv, mwConversation_CLOSED);
srvc = conv->service;
g_return_if_fail(srvc != NULL);
h = srvc->handler;
if(h && h->conversation_closed)
h->conversation_closed(conv, reason);
if(conv->channel) {
mwChannel_destroy(conv->channel, reason, NULL);
conv->channel = NULL;
}
}
void mwConversation_free(struct mwConversation *conv) {
g_return_if_fail(conv != NULL);
if(! mwConversation_isClosed(conv))
mwConversation_close(conv, ERR_SUCCESS);
convo_free(conv);
}