enum {
TYPING,
+ MESSAGE,
LAST_SIGNAL
};
ChimeConnection *cxn; /* For unsubscribing from jugg channels */
- GSList *members; /* Not including ourself */
+ GHashTable *members; /* Not including ourself */
gchar *channel;
gboolean favourite;
ChimeConversation *self = CHIME_CONVERSATION(object);
unsubscribe_conversation(NULL, self, NULL);
+ if (self->members) {
+ g_hash_table_destroy(self->members);
+ self->members = NULL;
+ }
printf("Conversation disposed: %p\n", self);
G_OBJECT_CLASS(chime_conversation_parent_class)->dispose(object);
g_signal_new ("typing",
G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST,
0, NULL, NULL, NULL, G_TYPE_NONE, 2, CHIME_TYPE_CONTACT, G_TYPE_BOOLEAN);
+
+ signals[MESSAGE] =
+ g_signal_new ("message",
+ G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL, NULL, G_TYPE_NONE, 1, JSON_TYPE_NODE);
}
+static void unref_member(gpointer obj)
+{
+ printf("Unref member %p\n", obj);
+ g_object_unref(obj);
+}
static void chime_conversation_init(ChimeConversation *self)
{
+ self->members = g_hash_table_new_full(g_str_hash, g_str_equal,
+ NULL, unref_member);
}
const gchar *chime_conversation_get_id(ChimeConversation *self)
return self->visibility;
}
+GList *chime_conversation_get_members(ChimeConversation *self)
+{
+ g_return_val_if_fail(CHIME_IS_CONVERSATION(self), FALSE);
+
+ return g_hash_table_get_values(self->members);
+}
+
+const gchar *chime_conversation_get_last_sent(ChimeConversation *self)
+{
+ g_return_val_if_fail(CHIME_IS_CONVERSATION(self), FALSE);
+
+ return self->last_sent;
+}
+
static gboolean parse_boolean(JsonNode *node, const gchar *member, gboolean *val)
{
gint64 intval;
return TRUE;
}
-static gboolean conv_membership_jugg_cb(ChimeConnection *cxn, gpointer _conv, JsonNode *data_node)
+static gboolean conv_membership_jugg_cb(ChimeConnection *cxn, gpointer _conv, JsonNode *node)
{
+ ChimeConversation *conv = CHIME_CONVERSATION(_conv);
+
+ JsonObject *obj = json_node_get_object(node);
+ JsonNode *record = json_object_get_member(obj, "record");
+ if (!record)
+ return FALSE;
+
+ obj = json_node_get_object(record);
+ JsonNode *member_node = json_object_get_member(obj, "Member");
+ if (!member_node)
+ return FALSE;
+
+ ChimeContact *member = chime_connection_parse_conversation_contact(cxn, member_node, NULL);
+ if (member_node) {
+ const gchar *id = chime_contact_get_profile_id(member);
+ g_hash_table_insert(conv->members, (gpointer)id, member);
+ return TRUE;
+ }
return FALSE;
}
conv_typing_jugg_cb, conv);
}
-static ChimeConversation *chime_connection_parse_conversation(ChimeConnection *cxn, JsonNode *node,
- GError **error)
+static void parse_members(ChimeConnection *cxn, ChimeConversation *conv, JsonNode *node)
+{
+ JsonArray *arr = json_node_get_array(node);
+ int i, len = json_array_get_length(arr);
+
+ for (i = 0; i < len; i++) {
+ ChimeContact *member = chime_connection_parse_conversation_contact(cxn,
+ json_array_get_element(arr, i), NULL);
+ if (member) {
+ const gchar *id = chime_contact_get_profile_id(member);
+ g_hash_table_insert(conv->members, (gpointer)id, member);
+ printf("Added %p %s\n", member, id);
+ }
+ }
+}
+
+ChimeConversation *chime_connection_parse_conversation(ChimeConnection *cxn, JsonNode *node,
+ GError **error)
{
ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE(cxn);
const gchar *id, *name, *channel, *created_on, *updated_on,
subscribe_conversation(cxn, conversation);
chime_object_collection_hash_object(&priv->conversations, CHIME_OBJECT(conversation), TRUE);
+ parse_members(cxn, conversation, members_node);
/* Emit signal on ChimeConnection to admit existence of new conversation */
- printf("new conv %s %p\n", id, conversation);
chime_connection_new_conversation(cxn, conversation);
return conversation;
}
chime_object_collection_hash_object(&priv->conversations, CHIME_OBJECT(conversation), TRUE);
+ parse_members(cxn, conversation, members_node);
return conversation;
}
chime_connection_queue_http_request(cxn, NULL, uri, "GET", conversations_cb,
NULL);
}
+
+
+struct deferred_conv_jugg {
+ JuggernautCallback cb;
+ JsonNode *node;
+};
+static void fetch_new_conv_cb(ChimeConnection *cxn, SoupMessage *msg, JsonNode *node,
+ gpointer _defer)
+{
+ ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
+ struct deferred_conv_jugg *defer = _defer;
+
+ if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) {
+ JsonObject *obj = json_node_get_object(node);
+ node = json_object_get_member(obj, "Conversation");
+ if (!node)
+ goto bad;
+
+ ChimeConversation *conv = chime_connection_parse_conversation(cxn, node, NULL);
+ if (!conv)
+ goto bad;
+
+ /* Sanity check; we don't want to just keep looping for ever if it goes wrong */
+ const gchar *conv_id;
+ if (!parse_string(node, "ConversationId", &conv_id))
+ goto bad;
+
+ conv = g_hash_table_lookup(priv->conversations.by_id, conv_id);
+ if (!conv)
+ goto bad;
+
+ /* OK, now we know about the new conversation we can play the msg node */
+ defer->cb(cxn, NULL, defer->node);
+ goto out;
+ }
+ bad:
+ ;
+ out:
+ json_node_unref(defer->node);
+ g_free(defer);
+}
+
+static gboolean conv_msg_jugg_cb(ChimeConnection *cxn, gpointer _unused, JsonNode *data_node)
+{
+ ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
+ JsonObject *obj = json_node_get_object(data_node);
+ JsonNode *record = json_object_get_member(obj, "record");
+ if (!record)
+ return FALSE;
+
+ const gchar *conv_id;
+ if (!parse_string(record, "ConversationId", &conv_id))
+ return FALSE;
+
+ ChimeConversation *conv = g_hash_table_lookup(priv->conversations.by_id,
+ conv_id);
+ if (!conv) {
+ /* It seems they don't do the helpful thing and send the notification
+ * of a new conversation before they send the first message. So let's
+ * go looking for it... */
+ struct deferred_conv_jugg *defer = g_new0(struct deferred_conv_jugg, 1);
+ defer->node = json_node_ref(data_node);
+ defer->cb = conv_msg_jugg_cb;
+
+ SoupURI *uri = soup_uri_new_printf(priv->messaging_url, "/conversations/%s", conv_id);
+ if (chime_connection_queue_http_request(cxn, NULL, uri, "GET", fetch_new_conv_cb, defer))
+ return TRUE;
+
+ json_node_unref(defer->node);
+ g_free(defer);
+ return FALSE;
+ }
+
+ const gchar *id;
+ if (!parse_string(record, "MessageId", &id))
+ return FALSE;
+
+ g_signal_emit(conv, signals[MESSAGE], 0, record);
+ return TRUE;
+}
+
static gboolean conv_jugg_cb(ChimeConnection *cxn, gpointer _unused, JsonNode *data_node)
{
JsonObject *obj = json_node_get_object(data_node);
chime_jugg_subscribe(cxn, priv->device_channel, "Conversation",
conv_jugg_cb, NULL);
+ chime_jugg_subscribe(cxn, priv->device_channel, "ConversationMessage",
+ conv_msg_jugg_cb, NULL);
+
fetch_conversations(cxn, NULL);
}
chime_jugg_unsubscribe(cxn, priv->device_channel, "Conversation",
conv_jugg_cb, NULL);
+ chime_jugg_unsubscribe(cxn, priv->device_channel, "ConversationMessage",
+ conv_msg_jugg_cb, NULL);
if (priv->conversations.by_id)
g_hash_table_foreach(priv->conversations.by_id, unsubscribe_conversation, NULL);
#include <prpl.h>
#include <blist.h>
+#include <debug.h>
#include "chime.h"
#include "chime-connection-private.h"
#include <libsoup/soup.h>
-struct chime_conversation {
- struct chime_msgs *msgs;
+struct chime_im {
+ struct chime_msgs msgs;
- ChimeConnection *cxn;
+ PurpleConnection *conn;
+ ChimeConversation *conv;
+ ChimeContact *peer;
- gchar *channel;
- gchar *id;
- gchar *name;
- gchar *visibility;
- gboolean favourite;
-
- GHashTable *members;
GHashTable *sent_msgs;
};
-static gboolean conv_membership_jugg_cb(ChimeConnection *cxn, gpointer _unused, JsonNode *record);
-static gboolean conv_typing_jugg_cb(ChimeConnection *cxn, gpointer _unused, JsonNode *record);
/* Called for all deliveries of incoming conversation messages, at startup and later */
-static gboolean do_conv_deliver_msg(ChimeConnection *cxn, struct chime_conversation *conv,
+static gboolean do_conv_deliver_msg(ChimeConnection *cxn, struct chime_im *im,
JsonNode *record, time_t msg_time)
{
ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
if (sys)
flags |= PURPLE_MESSAGE_SYSTEM;
- if (g_hash_table_size(conv->members) != 2) {
- /* Only 1:1 IM so far; representing multi-party conversations as chats comes later */
- return FALSE;
- }
-
if (strcmp(sender, priv->profile_id)) {
ChimeContact *contact = g_hash_table_lookup(priv->contacts.by_id,
sender);
} else {
const gchar *msg_id;
if (parse_string(record, "MessageId", &msg_id) &&
- g_hash_table_remove(conv->sent_msgs, msg_id)) {
+ g_hash_table_remove(im->sent_msgs, msg_id)) {
/* This was a message sent from this client. No need to display it. */
return TRUE;
}
- const gchar **member_ids;
- int peer;
-
- member_ids = (const gchar **)g_hash_table_get_keys_as_array(conv->members, NULL);
- if (!strcmp(member_ids[0], priv->profile_id))
- peer = 1;
- else
- peer = 0;
-
- ChimeContact *contact = g_hash_table_lookup(priv->contacts.by_id,
- member_ids[peer]);
- g_free(member_ids);
- if (!contact)
- return FALSE;
-
- const gchar *email = chime_contact_get_email(contact);
+ const gchar *email = chime_contact_get_email(im->peer);
/* Ick, how do we inject a message from ourselves? */
PurpleAccount *account = cxn->prpl_conn->account;
static void conv_deliver_msg(ChimeConnection *cxn, struct chime_msgs *msgs,
JsonNode *node, time_t msg_time)
{
- ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
- struct chime_conversation *conv = g_hash_table_lookup(priv->conversations_by_id, msgs->id);
+ struct chime_im *im = (struct chime_im *)msgs;
- do_conv_deliver_msg(cxn, conv, node, msg_time);
+ do_conv_deliver_msg(cxn, im, node, msg_time);
}
-static void fetch_conversation_messages(struct chime_conversation *conv)
-{
- if (conv->msgs) {
- if (conv->msgs->msgs_done)
- conv->msgs->msgs_done = FALSE;
- else
- return; /* Already in progress */
- } else {
- conv->msgs = g_new0(struct chime_msgs, 1);
- conv->msgs->id = conv->id;
- conv->msgs->members_done = TRUE;
- conv->msgs->cb = conv_deliver_msg;
- }
- printf("Fetch conv messages for %s\n", conv->id);
- fetch_messages(conv->cxn, conv->msgs, NULL);
-}
-static struct chime_conversation *one_conversation_cb(ChimeConnection *cxn, JsonNode *node)
+static void on_conv_msg(ChimeConversation *conv, JsonNode *record, struct chime_im *im)
{
- ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
- struct chime_conversation *conv;
- const gchar *channel, *id, *name, *visibility;
- gint64 favourite;
- JsonNode *members_node;
-
- if (!parse_string(node, "Channel", &channel) ||
- !parse_string(node, "ConversationId", &id) ||
- !parse_string(node, "Name", &name) ||
- !parse_string(node, "Visibility", &visibility) ||
- !parse_int(node, "Favorite", &favourite) ||
- !(members_node = json_object_get_member(json_node_get_object(node), "Members")))
- return NULL;
-
- conv = g_hash_table_lookup(priv->conversations_by_id, id);
- if (conv) {
- g_hash_table_remove(priv->conversations_by_name, conv->name);
- g_free(conv->channel);
- g_free(conv->name);
- g_free(conv->visibility);
- } else {
- conv = g_new0(struct chime_conversation, 1);
- conv->cxn = cxn;
- conv->id = g_strdup(id);
- g_hash_table_insert(priv->conversations_by_id, conv->id, conv);
- chime_jugg_subscribe(cxn, channel, NULL, NULL, NULL);
- chime_jugg_subscribe(cxn, channel, "ConversationMembership",
- conv_membership_jugg_cb, conv);
- chime_jugg_subscribe(cxn, channel, "TypingIndicator",
- conv_typing_jugg_cb, conv);
-
- /* Do we need to fetch new messages? */
- const gchar *last_seen, *last_sent;
-
- if (!chime_read_last_msg(cxn, FALSE, conv->id, &last_seen, NULL))
- last_seen = "1970-01-01T00:00:00.000Z";
-
- if (!parse_string(node, "LastSent", &last_sent) ||
- strcmp(last_seen, last_sent))
- fetch_conversation_messages(conv);
- }
-
- conv->channel = g_strdup(channel);
- conv->name = g_strdup(name);
- conv->visibility = g_strdup(visibility);
- conv->favourite = favourite;
- if (!conv->members)
- conv->members = g_hash_table_new(g_str_hash, g_str_equal);
- if (!conv->sent_msgs)
- conv->sent_msgs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
-
- JsonArray *arr = json_node_get_array(members_node);
- int i, len = json_array_get_length(arr);
- for (i = 0; i < len; i++) {
- ChimeContact *member = chime_connection_parse_conversation_contact(cxn,
- json_array_get_element(arr, i), NULL);
- if (member) {
- const gchar *id = chime_contact_get_profile_id(member);
- g_hash_table_insert(conv->members, (gpointer)id, member);
- if (len == 2 && strcmp(id, priv->profile_id)) {
- /* A two-party conversation contains only us (presumably!)
- * and one other. Index 1:1 conversations on that "other" */
- g_hash_table_insert(priv->im_conversations_by_peer_id,
- (void *)id, conv);
-
- printf("im_member for %s (%d) %s\n", conv->id, len, id);
- }
- }
- }
-
- g_hash_table_insert(priv->conversations_by_name, conv->name, conv);
- return conv;
-}
-
-static void fetch_conversations(ChimeConnection *cxn, const gchar *next_token);
-static void conversationlist_cb(ChimeConnection *cxn, SoupMessage *msg,
- JsonNode *node, gpointer convlist)
-{
- const gchar *next_token;
-
- if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code) && node) {
- JsonNode *convs_node;
- JsonObject *obj;
- JsonArray *convs_arr;
-
- obj = json_node_get_object(node);
- convs_node = json_object_get_member(obj, "Conversations");
- if (convs_node) {
- convs_arr = json_node_get_array(convs_node);
- guint i, len = json_array_get_length(convs_arr);
- for (i = 0; i < len; i++)
- one_conversation_cb(cxn, json_array_get_element(convs_arr, i));
- }
- if (parse_string(node, "NextToken", &next_token))
- fetch_conversations(cxn, next_token);
- else {
- ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
- if (!priv->convs_online) {
- priv->convs_online = TRUE;
- chime_connection_calculate_online(cxn);
- }
- }
- }
-}
-
-static void fetch_conversations(ChimeConnection *cxn, const gchar *next_token)
-{
- ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
- SoupURI *uri = soup_uri_new_printf(priv->messaging_url, "/conversations");
-
- soup_uri_set_query_from_fields(uri, "max-results", "50",
- next_token ? "next-token" : NULL, next_token,
- NULL);
- chime_connection_queue_http_request(cxn, NULL, uri, "GET", conversationlist_cb, NULL);
-}
-
-static void destroy_conversation(gpointer _conv)
-{
- struct chime_conversation *conv = _conv;
-
- chime_jugg_unsubscribe(conv->cxn, conv->channel, NULL, NULL, NULL);
- chime_jugg_unsubscribe(conv->cxn, conv->channel, "ConversationMembership",
- conv_membership_jugg_cb, conv);
- chime_jugg_unsubscribe(conv->cxn, conv->channel, "TypingIndicator",
- conv_typing_jugg_cb, conv);
- g_free(conv->id);
- g_free(conv->channel);
- g_free(conv->name);
- g_free(conv->visibility);
- g_hash_table_destroy(conv->members);
- g_hash_table_destroy(conv->sent_msgs);
- conv->members = NULL;
- g_free(conv);
-}
-
-static gboolean conv_jugg_cb(ChimeConnection *cxn, gpointer _unused, JsonNode *data_node)
-{
- JsonObject *obj = json_node_get_object(data_node);
- JsonNode *record = json_object_get_member(obj, "record");
- if (!record)
- return FALSE;
-
- return !!one_conversation_cb(cxn, record);
-}
-
-struct deferred_conv_jugg {
- JuggernautCallback cb;
- JsonNode *node;
-};
-static void fetch_new_conv_cb(ChimeConnection *cxn, SoupMessage *msg, JsonNode *node,
- gpointer _defer)
-{
- ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
- struct deferred_conv_jugg *defer = _defer;
-
- if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) {
- JsonObject *obj = json_node_get_object(node);
- node = json_object_get_member(obj, "Conversation");
- if (!node)
- goto bad;
-
- if (!one_conversation_cb(cxn, node))
- goto bad;
-
- /* Sanity check; we don't want to just keep looping for ever if it goes wrong */
- const gchar *conv_id;
- if (!parse_string(node, "ConversationId", &conv_id))
- goto bad;
-
- struct chime_conversation *conv = g_hash_table_lookup(priv->conversations_by_id,
- conv_id);
- if (!conv)
- goto bad;
-
- /* OK, now we know about the new conversation we can play the msg node */
- defer->cb(cxn, NULL, defer->node);
- goto out;
- }
- bad:
- ;
- out:
- json_node_unref(defer->node);
- g_free(defer);
-}
-
-static gboolean conv_msg_jugg_cb(ChimeConnection *cxn, gpointer _unused, JsonNode *data_node)
-{
- ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
- JsonObject *obj = json_node_get_object(data_node);
- JsonNode *record = json_object_get_member(obj, "record");
- if (!record)
- return FALSE;
-
- const gchar *conv_id;
- if (!parse_string(record, "ConversationId", &conv_id))
- return FALSE;
-
- struct chime_conversation *conv = g_hash_table_lookup(priv->conversations_by_id,
- conv_id);
- if (!conv) {
- /* It seems they don't do the helpful thing and send the notification
- * of a new conversation before they send the first message. So let's
- * go looking for it... */
- struct deferred_conv_jugg *defer = g_new0(struct deferred_conv_jugg, 1);
- defer->node = json_node_ref(data_node);
- defer->cb = conv_msg_jugg_cb;
-
- SoupURI *uri = soup_uri_new_printf(priv->messaging_url, "/conversations/%s", conv_id);
- if (chime_connection_queue_http_request(cxn, NULL, uri, "GET", fetch_new_conv_cb, defer))
- return TRUE;
-
- json_node_unref(defer->node);
- g_free(defer);
- return FALSE;
- }
-
+ ChimeConnection *cxn = CHIME_CONNECTION(purple_connection_get_protocol_data(im->conn));
const gchar *id;
if (!parse_string(record, "MessageId", &id))
- return FALSE;
- if (conv->msgs && conv->msgs->messages) {
+ return;
+ if (im->msgs.messages) {
/* Still gathering messages. Add to the table, to avoid dupes */
- g_hash_table_insert(conv->msgs->messages, (gchar *)id, json_node_ref(record));
- return TRUE;
+ g_hash_table_insert(im->msgs.messages, (gchar *)id, json_node_ref(record));
+ return;
}
GTimeVal tv;
const gchar *created;
if (!parse_time(record, "CreatedOn", &created, &tv))
- return FALSE;
+ return;
- chime_update_last_msg(cxn, FALSE, conv->id, created, id);
+ chime_update_last_msg(cxn, FALSE, chime_object_get_id(CHIME_OBJECT(im->conv)), created, id);
- return do_conv_deliver_msg(cxn, conv, record, tv.tv_sec);
+ do_conv_deliver_msg(cxn, im, record, tv.tv_sec);
}
-static gboolean conv_membership_jugg_cb(ChimeConnection *cxn, gpointer _unused, JsonNode *data_node)
+static void on_conv_typing(ChimeConversation *conv, ChimeContact *contact, gboolean state, struct chime_im *im)
{
- ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
- JsonObject *obj = json_node_get_object(data_node);
- JsonNode *record = json_object_get_member(obj, "record");
- if (!record)
- return FALSE;
-
- const gchar *conv_id;
- if (!parse_string(record, "ConversationId", &conv_id))
- return FALSE;
-
- struct chime_conversation *conv = g_hash_table_lookup(priv->conversations_by_id,
- conv_id);
- if (!conv) {
- /* It seems they don't do the helpful thing and send the notification
- * of a new conversation before they send the first message. So let's
- * go looking for it... */
- struct deferred_conv_jugg *defer = g_new0(struct deferred_conv_jugg, 1);
- defer->node = json_node_ref(data_node);
- defer->cb = conv_membership_jugg_cb;
-
- SoupURI *uri = soup_uri_new_printf(priv->messaging_url, "/conversations/%s", conv_id);
- if (chime_connection_queue_http_request(cxn, NULL, uri, "GET", fetch_new_conv_cb, defer))
- return TRUE;
-
- json_node_unref(defer->node);
- g_free(defer);
- return FALSE;
- }
-
-#if 0 /* We think we fixed this by reregistering the device as OSX not Android */
- /* WTF? Some users get only ConversationMembership updates when a new message
- comes in? */
- obj = json_node_get_object(record);
- JsonNode *member = json_object_get_member(obj, "Member");
- if (!member)
- return FALSE;
-
- /* Has this member seen a message later than the last one we have? */
- const gchar *last_seen, *last_delivered;
-
- if (!chime_read_last_msg(cxn, FALSE, conv->id, &last_seen, NULL))
- last_seen = "1970-01-01T00:00:00.000Z";
+ ChimeConnection *cxn = CHIME_CONNECTION(purple_connection_get_protocol_data(im->conn));
+ const gchar *email = chime_contact_get_email(contact);
- if (!parse_string(member, "LastDelivered", &last_delivered) ||
- strcmp(last_seen, last_delivered)) {
- printf("WTF refetching messages for ConversationMembership update\n");
- fetch_conversation_messages(conv);
- } else printf("no fetch last %s\n", last_seen);
-#endif
- return TRUE;
+ if (state)
+ serv_got_typing(cxn->prpl_conn, email, 0, PURPLE_TYPING);
+ else
+ serv_got_typing_stopped(cxn->prpl_conn, email);
}
-static gboolean conv_typing_jugg_cb(ChimeConnection *cxn, gpointer _conv, JsonNode *data_node)
+void on_chime_new_conversation(ChimeConnection *cxn, ChimeConversation *conv, PurpleConnection *conn)
{
ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
- struct chime_conversation *conv = _conv;
-
- gint64 state;
- if (!parse_int(data_node, "state", &state))
- return FALSE;
-
- JsonNode *node = json_node_get_parent(data_node);
- if (!node)
- return FALSE;
-
- JsonObject *obj = json_node_get_object(node);
- node = json_object_get_member(obj, "from");
-
- const gchar *from;
- if (!node || !parse_string(node, "id", &from))
- return FALSE;
- ChimeContact *contact = g_hash_table_lookup(priv->contacts.by_id, from);
- if (!contact)
- return FALSE;
-
- if (g_hash_table_size(conv->members) != 2) {
- /* Only 1:1 so far */
- return FALSE;
+ GList *members = chime_conversation_get_members(conv);
+ if (g_list_length(members) != 2) {
+ /* We don't support non-1:1 conversations yet. We need to handle them as 'chats'. */
+ return;
}
+ struct chime_im *im = g_new0(struct chime_im, 1);
+ im->msgs.id = chime_object_get_id(CHIME_OBJECT(conv));
+ im->msgs.members_done = TRUE;
+ im->msgs.cb = conv_deliver_msg;
+ im->conn = conn;
+ im->sent_msgs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+ im->conv = g_object_ref(conv);
+ im->peer = members->data;
+ if (!strcmp(priv->profile_id, chime_object_get_id(CHIME_OBJECT(im->peer))))
+ im->peer = members->next->data;
+ g_list_free(members);
+ g_object_ref(im->peer);
+ purple_debug(PURPLE_DEBUG_INFO, "chime", "New conversation %s with %s\n", chime_object_get_id(CHIME_OBJECT(im->peer)),
+ chime_contact_get_email(im->peer));
+
+ g_hash_table_insert(priv->im_conversations_by_peer_id,
+ (void *)chime_object_get_id(CHIME_OBJECT(im->peer)), im);
+
+ g_signal_connect(conv, "typing", G_CALLBACK(on_conv_typing), im);
+ g_signal_connect(conv, "message", G_CALLBACK(on_conv_msg), im);
+
+ /* Do we need to fetch new messages? */
+ const gchar *last_seen, *last_sent;
+
+ last_sent = chime_conversation_get_last_sent(conv);
+
+ if (!chime_read_last_msg(cxn, FALSE, chime_object_get_id(CHIME_OBJECT(conv)), &last_seen, NULL))
+ last_seen = "1970-01-01T00:00:00.000Z";
- const gchar *email = chime_contact_get_email(contact);
+ if (last_sent && strcmp(last_seen, last_sent)) {
+ purple_debug(PURPLE_DEBUG_INFO, "chime", "Fetch conv messages for %s\n", im->msgs.id);
+ fetch_messages(purple_connection_get_protocol_data(im->conn), &im->msgs, NULL);
+ }
+}
- if (state)
- serv_got_typing(cxn->prpl_conn, email, 0, PURPLE_TYPING);
- else
- serv_got_typing_stopped(cxn->prpl_conn, email);
+static void im_destroy(gpointer _im)
+{
+ struct chime_im *im = _im;
- return TRUE;
+ g_hash_table_destroy(im->sent_msgs);
+ g_object_unref(im->conv);
+ g_object_unref(im->peer);
+ g_free(im);
}
-void chime_init_conversations_old(ChimeConnection *cxn)
+void purple_chime_init_conversations(ChimeConnection *cxn)
{
ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
-
- priv->im_conversations_by_peer_id = g_hash_table_new(g_str_hash, g_str_equal);
- priv->conversations_by_name = g_hash_table_new(g_str_hash, g_str_equal);
- priv->conversations_by_id = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, destroy_conversation);
- chime_jugg_subscribe(cxn, priv->device_channel, "ConversationMembership",
- conv_membership_jugg_cb, NULL);
- chime_jugg_subscribe(cxn, priv->device_channel, "ConversationMessage",
- conv_msg_jugg_cb, NULL);
- chime_jugg_subscribe(cxn, priv->device_channel, "Conversation",
- conv_jugg_cb, NULL);
- fetch_conversations(cxn, NULL);
+ priv->im_conversations_by_peer_id = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, im_destroy);
}
-void chime_destroy_conversations_old(ChimeConnection *cxn)
+void purple_chime_destroy_conversations(ChimeConnection *cxn)
{
ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
- chime_jugg_unsubscribe(cxn, priv->device_channel, "ConversationMembership",
- conv_membership_jugg_cb, NULL);
- chime_jugg_unsubscribe(cxn, priv->device_channel, "ConversationMessage",
- conv_msg_jugg_cb, NULL);
- chime_jugg_unsubscribe(cxn, priv->device_channel, "Conversation",
- conv_jugg_cb, NULL);
- g_hash_table_destroy(priv->im_conversations_by_peer_id);
- g_hash_table_destroy(priv->conversations_by_name);
- g_hash_table_destroy(priv->conversations_by_id);
- priv->conversations_by_name = priv->conversations_by_id = NULL;
+ g_clear_pointer(&priv->im_conversations_by_peer_id, g_hash_table_destroy);
}
struct im_send_data {
- struct chime_conversation *conv;
+ struct chime_im *im;
gchar *who;
gchar *message;
PurpleMessageFlags flags;
};
-static void im_error(ChimeConnection *cxn, struct im_send_data *im,
+static void im_error(ChimeConnection *cxn, struct im_send_data *imd,
const gchar *format, ...)
{
va_list args;
va_end(args);
PurpleConversation *pconv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY,
- im->who,
+ imd->who,
cxn->prpl_conn->account);
if (pconv)
purple_conversation_write(pconv, NULL, msg, PURPLE_MESSAGE_ERROR, time(NULL));
g_free(msg);
}
-static void send_im_cb(ChimeConnection *cxn, SoupMessage *msg, JsonNode *node, gpointer _im)
+static void send_im_cb(ChimeConnection *cxn, SoupMessage *msg, JsonNode *node, gpointer _imd)
{
- struct im_send_data *im = _im;
+ struct im_send_data *imd = _imd;
/* Nothing to do o nsuccess */
if (!SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) {
- im_error(cxn, im, _("Failed to send message(%d): %s\n"),
+ im_error(cxn, imd, _("Failed to send message(%d): %s\n"),
msg->status_code, msg->reason_phrase);
} else {
JsonObject *obj = json_node_get_object(node);
JsonNode *node = json_object_get_member(obj, "Message");
const gchar *msg_id;
if (node && parse_string(node, "MessageId", &msg_id)) {
- g_hash_table_add(im->conv->sent_msgs, g_strdup(msg_id));
+ g_hash_table_add(imd->im->sent_msgs, g_strdup(msg_id));
}
}
- g_free(im->message);
- g_free(im);
+ g_free(imd->message);
+ g_free(imd);
}
unsigned int chime_send_typing(PurpleConnection *conn, const char *name, PurpleTypingState state)
return 0;
const gchar *id = chime_contact_get_profile_id(contact);
- struct chime_conversation *conv = g_hash_table_lookup(priv->im_conversations_by_peer_id,
- id);
- if (!conv)
+ struct chime_im *imd = g_hash_table_lookup(priv->im_conversations_by_peer_id, id);
+ if (!imd)
return 0;
JsonBuilder *jb = json_builder_new();
jb = json_builder_begin_object(jb);
jb = json_builder_set_member_name(jb, "channel");
- jb = json_builder_add_string_value(jb, conv->channel);
+ jb = json_builder_add_string_value(jb, chime_conversation_get_channel(imd->conv));
jb = json_builder_set_member_name(jb, "data");
jb = json_builder_begin_object(jb);
jb = json_builder_set_member_name(jb, "klass");
return 0;
}
-static int send_im(ChimeConnection *cxn, struct im_send_data *im)
+static int send_im(ChimeConnection *cxn, struct im_send_data *imd)
{
ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
JsonBuilder *jb = json_builder_new();
jb = json_builder_begin_object(jb);
jb = json_builder_set_member_name(jb, "Content");
- jb = json_builder_add_string_value(jb, im->message);
+ jb = json_builder_add_string_value(jb, imd->message);
jb = json_builder_set_member_name(jb, "ClientRequestToken");
jb = json_builder_add_string_value(jb, uuid);
jb = json_builder_end_object(jb);
int ret;
SoupURI *uri = soup_uri_new_printf(priv->messaging_url, "/conversations/%s/messages",
- im->conv->id);
+ chime_object_get_id(CHIME_OBJECT(imd->im->conv)));
JsonNode *node = json_builder_get_root(jb);
- if (chime_connection_queue_http_request(cxn, node, uri, "POST", send_im_cb, im))
+ if (chime_connection_queue_http_request(cxn, node, uri, "POST", send_im_cb, imd))
ret = 1;
else {
ret = -1;
- g_free(im->who);
- g_free(im->message);
- g_free(im);
+ g_free(imd->who);
+ g_free(imd->message);
+ g_free(imd);
}
json_node_unref(node);
return ret;
}
-static void conv_create_cb(ChimeConnection *cxn, SoupMessage *msg, JsonNode *node, gpointer _im)
+static void conv_create_cb(ChimeConnection *cxn, SoupMessage *msg, JsonNode *node, gpointer _imd)
{
- struct im_send_data *im = _im;
+ ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
+ struct im_send_data *imd = _imd;
if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) {
JsonObject *obj = json_node_get_object(node);
if (!node)
goto bad;
- im->conv = one_conversation_cb(cxn, node);
- if (!im->conv)
+ if (!chime_connection_parse_conversation(cxn, node, NULL)) {
+ printf("No conversation");
+ goto bad;
+ }
+
+ ChimeContact *contact = g_hash_table_lookup(priv->contacts.by_name, imd->who);
+ if (!contact) {
+ printf("no contact for %s\n", imd->who);
+ goto bad;
+ }
+
+ const gchar *id = chime_contact_get_profile_id(contact);
+ imd->im = g_hash_table_lookup(priv->im_conversations_by_peer_id, id);
+ if (!imd->im) {
+ printf("No im for %s\n", id);
goto bad;
+ }
- send_im(cxn, im);
+ send_im(cxn, imd);
return;
}
bad:
- im_error(cxn, im, _("Failed to create IM conversation"));
- g_free(im->who);
- g_free(im->message);
- g_free(im);
+ im_error(cxn, imd, _("Failed to create IM conversation"));
+ g_free(imd->who);
+ g_free(imd->message);
+ g_free(imd);
}
- static int create_im_conv(ChimeConnection *cxn, struct im_send_data *im, const gchar *profile_id)
+static int create_im_conv(ChimeConnection *cxn, struct im_send_data *im, const gchar *profile_id)
{
ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
JsonBuilder *jb = json_builder_new();
return ret;
}
-static void autocomplete_im_cb(ChimeConnection *cxn, SoupMessage *msg, JsonNode *node, gpointer _im)
+static void autocomplete_im_cb(ChimeConnection *cxn, SoupMessage *msg, JsonNode *node, gpointer _imd)
{
- struct im_send_data *im = _im;
+ struct im_send_data *imd = _imd;
if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) {
JsonArray *arr = json_node_get_array(node);
if (parse_string(node, "email", &email) &&
parse_string(node, "id", &profile_id) &&
- !strcmp(im->who, email)) {
- create_im_conv(cxn, im, profile_id);
+ !strcmp(imd->who, email)) {
+ create_im_conv(cxn, imd, profile_id);
return;
}
}
}
- im_error(cxn, im, _("Failed to find user"));
- g_free(im->who);
- g_free(im->message);
- g_free(im);
+ im_error(cxn, imd, _("Failed to find user"));
+ g_free(imd->who);
+ g_free(imd->message);
+ g_free(imd);
}
int chime_purple_send_im(PurpleConnection *gc, const char *who, const char *message, PurpleMessageFlags flags)
ChimeConnection *cxn = purple_connection_get_protocol_data(gc);
ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
- struct im_send_data *im = g_new0(struct im_send_data, 1);
- im->message = purple_unescape_html(message);
- im->who = g_strdup(who);
- im->flags = flags;
+ struct im_send_data *imd = g_new0(struct im_send_data, 1);
+ imd->message = purple_unescape_html(message);
+ imd->who = g_strdup(who);
+ imd->flags = flags;
ChimeContact *contact = g_hash_table_lookup(priv->contacts.by_name, who);
if (contact) {
const gchar *id = chime_contact_get_profile_id(contact);
- im->conv = g_hash_table_lookup(priv->im_conversations_by_peer_id,
- id);
- if (im->conv)
- return send_im(cxn, im);
+ imd->im = g_hash_table_lookup(priv->im_conversations_by_peer_id, id);
+ if (imd->im)
+ return send_im(cxn, imd);
- return create_im_conv(cxn, im, id);
+ return create_im_conv(cxn, imd, id);
}
SoupURI *uri = soup_uri_new_printf(priv->contacts_url, "/registered_auto_completes");
int ret;
JsonNode *node = json_builder_get_root(jb);
- if (chime_connection_queue_http_request(cxn, node, uri, "POST", autocomplete_im_cb, im))
+ if (chime_connection_queue_http_request(cxn, node, uri, "POST", autocomplete_im_cb, imd))
ret = 1;
else {
ret = -1;
- g_free(im->who);
- g_free(im->message);
- g_free(im);
+ g_free(imd->who);
+ g_free(imd->message);
+ g_free(imd);
}
json_node_unref(node);