summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordx <dx@dxzone.com.ar>2017-06-25 04:01:56 -0300
committerdx <dx@dxzone.com.ar>2017-06-25 04:01:56 -0300
commitf7b1dcd720ded17ea70068260658d41962f21d80 (patch)
tree2d11e6d5a9b8b1d34108c2c1212ff5af0d4475e5
parente744c6f2d68e4c89a525c81e19b15e2fc7b9a809 (diff)
downloadpidgin-f7b1dcd720ded17ea70068260658d41962f21d80.tar.gz
facebook: Use FetchContactsDeltaQuery for contact sync
This has a number of benefits: - Most of the time the contact sync reply will be empty - We can do contact sync more frequently (It's 5 mins now, was 30) - Figuring out what contacts were added or removed is much simpler and less likely to get things wrong. - Non-friends are no longer accidentally removed because there's no need to compare contact lists - On accounts with lots of friends this gets rid of one source of CPU usage spikes - Less load for facebook's servers (lol)
-rw-r--r--libpurple/protocols/facebook/api.c155
-rw-r--r--libpurple/protocols/facebook/facebook.c76
2 files changed, 201 insertions, 30 deletions
diff --git a/libpurple/protocols/facebook/api.c b/libpurple/protocols/facebook/api.c
index 55462dc509..517a14aa4d 100644
--- a/libpurple/protocols/facebook/api.c
+++ b/libpurple/protocols/facebook/api.c
@@ -67,6 +67,7 @@ struct _FbApiPrivate
gboolean invisible;
guint unread;
FbId lastmid;
+ gchar *contacts_delta;
};
struct _FbApiData
@@ -87,6 +88,9 @@ fb_api_message_send(FbApi *api, FbApiMessage *msg);
static void
fb_api_sticker(FbApi *api, FbId sid, FbApiMessage *msg);
+void
+fb_api_contacts_delta(FbApi *api, const gchar *delta_cursor);
+
G_DEFINE_TYPE(FbApi, fb_api, G_TYPE_OBJECT);
static void
@@ -184,6 +188,7 @@ fb_api_dispose(GObject *obj)
g_free(priv->did);
g_free(priv->stoken);
g_free(priv->token);
+ g_free(priv->contacts_delta);
}
static void
@@ -342,6 +347,22 @@ fb_api_class_init(FbApiClass *klass)
2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
/**
+ * FbApi::contacts-delta:
+ * @api: The #FbApi.
+ * @added: The #GSList of added #FbApiUser's.
+ * @removed: The #GSList of strings with removed user ids.
+ *
+ * Like 'contacts', but only the deltas.
+ */
+ g_signal_new("contacts-delta",
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_ACTION,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 2, G_TYPE_POINTER, G_TYPE_POINTER);
+
+ /**
* FbApi::error:
* @api: The #FbApi.
* @error: The #GError.
@@ -2067,6 +2088,7 @@ fb_api_cb_contacts_nodes(FbApi *api, JsonNode *root, GSList *users)
FbApiUser *user;
FbId uid;
FbJsonValues *values;
+ gboolean is_array;
GError *err = NULL;
values = fb_json_values_new(root);
@@ -2078,8 +2100,12 @@ fb_api_cb_contacts_nodes(FbApi *api, JsonNode *root, GSList *users)
"$.structured_name.text");
fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
"$.hugePictureUrl.uri");
- fb_json_values_set_array(values, FALSE, "$.viewer.messenger_contacts"
- ".nodes");
+
+ is_array = (JSON_NODE_TYPE(root) == JSON_NODE_ARRAY);
+
+ if (is_array) {
+ fb_json_values_set_array(values, FALSE, "$");
+ }
while (fb_json_values_update(values, &err)) {
str = fb_json_values_next_str(values, "0");
@@ -2089,6 +2115,9 @@ fb_api_cb_contacts_nodes(FbApi *api, JsonNode *root, GSList *users)
if ((!purple_strequal(str, "ARE_FRIENDS") &&
(uid != priv->uid)) || (uid == 0))
{
+ if (!is_array) {
+ break;
+ }
continue;
}
@@ -2100,6 +2129,10 @@ fb_api_cb_contacts_nodes(FbApi *api, JsonNode *root, GSList *users)
user->csum = fb_api_user_icon_checksum(user->icon);
users = g_slist_prepend(users, user);
+
+ if (!is_array) {
+ break;
+ }
}
g_object_unref(values);
@@ -2107,35 +2140,112 @@ fb_api_cb_contacts_nodes(FbApi *api, JsonNode *root, GSList *users)
return users;
}
+/* base64(contact:<our id>:<their id>:<whatever>) */
+static GSList *
+fb_api_cb_contacts_parse_removed(FbApi *api, JsonNode *node, GSList *users)
+{
+ gsize len;
+ char **split;
+ char *decoded = (char *) g_base64_decode(json_node_get_string(node), &len);
+
+ g_return_val_if_fail(decoded[len] == '\0', users);
+ g_return_val_if_fail(len == strlen(decoded), users);
+ g_return_val_if_fail(g_str_has_prefix(decoded, "contact:"), users);
+
+ split = g_strsplit_set(decoded, ":", 4);
+
+ g_return_val_if_fail(g_strv_length(split) == 4, users);
+
+ users = g_slist_prepend(users, g_strdup(split[2]));
+
+ g_strfreev(split);
+ g_free(decoded);
+
+ return users;
+}
+
static void
fb_api_cb_contacts(PurpleHttpConnection *con, PurpleHttpResponse *res,
gpointer data)
{
- //XXX
const gchar *cursor;
+ const gchar *delta_cursor;
FbApi *api = data;
+ FbApiPrivate *priv = api->priv;
FbJsonValues *values;
gboolean complete;
+ gboolean is_delta;
GError *err = NULL;
+ GList *l;
GSList *users = NULL;
JsonNode *root;
+ JsonNode *croot;
+ JsonNode *node;
if (!fb_api_http_chk(api, con, res, &root)) {
return;
}
- users = fb_api_cb_contacts_nodes(api, root, users);
+ croot = fb_json_node_get(root, "$.viewer.messenger_contacts.deltas", NULL);
+ is_delta = (croot != NULL);
- values = fb_json_values_new(root);
+ if (!is_delta) {
+ croot = fb_json_node_get(root, "$.viewer.messenger_contacts", NULL);
+ node = fb_json_node_get(croot, "$.nodes", NULL);
+ users = fb_api_cb_contacts_nodes(api, node, users);
+ json_node_free(node);
+
+ } else {
+ GSList *added = NULL;
+ GSList *removed = NULL;
+ JsonArray *arr = fb_json_node_get_arr(croot, "$.nodes", NULL);
+ GList *elms = json_array_get_elements(arr);
+
+ for (l = elms; l != NULL; l = l->next) {
+ if ((node = fb_json_node_get(l->data, "$.added", NULL))) {
+ added = fb_api_cb_contacts_nodes(api, node, added);
+ json_node_free(node);
+ }
+
+ if ((node = fb_json_node_get(l->data, "$.removed", NULL))) {
+ removed = fb_api_cb_contacts_parse_removed(api, node, removed);
+ json_node_free(node);
+ }
+ }
+
+ g_signal_emit_by_name(api, "contacts-delta", added, removed);
+
+ g_slist_free_full(added, (GDestroyNotify) fb_api_user_free);
+ g_slist_free_full(removed, (GDestroyNotify) g_free);
+
+ g_list_free(elms);
+ json_array_unref(arr);
+ }
+
+ values = fb_json_values_new(croot);
+ fb_json_values_add(values, FB_JSON_TYPE_BOOL, FALSE,
+ "$.page_info.has_next_page");
fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
- "$.viewer.messenger_contacts.page_info.end_cursor");
+ "$.page_info.delta_cursor");
+ fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
+ "$.page_info.end_cursor");
fb_json_values_update(values, NULL);
+ complete = !fb_json_values_next_bool(values, FALSE);
+
+ delta_cursor = fb_json_values_next_str(values, NULL);
+
cursor = fb_json_values_next_str(values, NULL);
if (G_UNLIKELY(err == NULL)) {
- complete = (cursor == NULL);
- g_signal_emit_by_name(api, "contacts", users, complete);
+ if (is_delta || complete) {
+ g_free(priv->contacts_delta);
+ priv->contacts_delta = g_strdup(is_delta ? cursor : delta_cursor);
+ }
+
+ if (users) {
+ g_signal_emit_by_name(api, "contacts", users, complete);
+ }
if (!complete) {
fb_api_contacts_after(api, cursor);
@@ -2146,14 +2256,25 @@ fb_api_cb_contacts(PurpleHttpConnection *con, PurpleHttpResponse *res,
g_slist_free_full(users, (GDestroyNotify) fb_api_user_free);
g_object_unref(values);
+
+ json_node_free(croot);
json_node_free(root);
}
void
fb_api_contacts(FbApi *api)
{
+ FbApiPrivate *priv;
JsonBuilder *bldr;
+ g_return_if_fail(FB_IS_API(api));
+ priv = api->priv;
+
+ if (priv->contacts_delta) {
+ fb_api_contacts_delta(api, priv->contacts_delta);
+ return;
+ }
+
bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
fb_json_bldr_arr_begin(bldr, "0");
fb_json_bldr_add_str(bldr, NULL, "user");
@@ -2181,6 +2302,24 @@ fb_api_contacts_after(FbApi *api, const gchar *cursor)
}
void
+fb_api_contacts_delta(FbApi *api, const gchar *delta_cursor)
+{
+ JsonBuilder *bldr;
+
+ bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
+
+ fb_json_bldr_add_str(bldr, "0", delta_cursor);
+
+ fb_json_bldr_arr_begin(bldr, "1");
+ fb_json_bldr_add_str(bldr, NULL, "user");
+ fb_json_bldr_arr_end(bldr);
+
+ fb_json_bldr_add_str(bldr, "2", G_STRINGIFY(FB_API_CONTACTS_COUNT));
+ fb_api_http_query(api, FB_API_QUERY_CONTACTS_DELTA, bldr,
+ fb_api_cb_contacts);
+}
+
+void
fb_api_connect(FbApi *api, gboolean invisible)
{
FbApiPrivate *priv;
diff --git a/libpurple/protocols/facebook/facebook.c b/libpurple/protocols/facebook/facebook.c
index 8e7f8b2bcb..c1d28ccc2f 100644
--- a/libpurple/protocols/facebook/facebook.c
+++ b/libpurple/protocols/facebook/facebook.c
@@ -221,11 +221,11 @@ fb_sync_contacts_add_timeout(FbData *fata)
gc = fb_data_get_connection(fata);
acct = purple_connection_get_account(gc);
- sync = purple_account_get_int(acct, "sync-interval", 30);
+ sync = purple_account_get_int(acct, "sync-interval", 5);
- if (sync < 5) {
- purple_account_set_int(acct, "sync-interval", 5);
- sync = 5;
+ if (sync < 1) {
+ purple_account_set_int(acct, "sync-interval", 1);
+ sync = 1;
}
sync *= 60 * 1000;
@@ -242,8 +242,6 @@ fb_cb_api_contacts(FbApi *api, GSList *users, gboolean complete, gpointer data)
FbData *fata = data;
FbId muid;
gchar uid[FB_ID_STRMAX];
- gpointer bata;
- GSList *buddies;
GSList *l;
GValue val = G_VALUE_INIT;
PurpleAccount *acct;
@@ -293,7 +291,6 @@ fb_cb_api_contacts(FbApi *api, GSList *users, gboolean complete, gpointer data)
purple_blist_add_buddy(bdy, NULL, grp, NULL);
}
- purple_buddy_set_protocol_data(bdy, GINT_TO_POINTER(TRUE));
purple_buddy_set_server_alias(bdy, user->name);
csum = purple_buddy_icons_get_checksum_for_user(bdy);
@@ -309,20 +306,6 @@ fb_cb_api_contacts(FbApi *api, GSList *users, gboolean complete, gpointer data)
return;
}
- buddies = purple_blist_find_buddies(acct, NULL);
-
- while (buddies != NULL) {
- bdy = buddies->data;
- bata = purple_buddy_get_protocol_data(bdy);
- buddies = g_slist_delete_link(buddies, buddies);
-
- if (GPOINTER_TO_INT(bata)) {
- purple_buddy_set_protocol_data(bdy, NULL);
- } else if (purple_buddy_get_group(bdy) != grpn) {
- purple_blist_remove_buddy(bdy);
- }
- }
-
if (state != PURPLE_CONNECTION_CONNECTED) {
status = purple_account_get_active_status(acct);
type = purple_status_get_status_type(status);
@@ -336,6 +319,51 @@ fb_cb_api_contacts(FbApi *api, GSList *users, gboolean complete, gpointer data)
}
static void
+fb_cb_api_contacts_delta(FbApi *api, GSList *added, GSList *removed, gpointer data)
+{
+ FbApiUser *user;
+ FbData *fata = data;
+ gchar uid[FB_ID_STRMAX];
+ GSList *l;
+ PurpleAccount *acct;
+ PurpleBuddy *bdy;
+ PurpleConnection *gc;
+ PurpleGroup *grp;
+ PurpleGroup *grpn;
+
+ gc = fb_data_get_connection(fata);
+ acct = purple_connection_get_account(gc);
+ grp = fb_get_group(TRUE);
+ grpn = fb_get_group(FALSE);
+
+ for (l = added; l != NULL; l = l->next) {
+ user = l->data;
+ FB_ID_TO_STR(user->uid, uid);
+
+ bdy = purple_blist_find_buddy(acct, uid);
+
+ if ((bdy != NULL) && (purple_buddy_get_group(bdy) == grpn)) {
+ purple_blist_remove_buddy(bdy);
+ }
+
+ bdy = purple_buddy_new(acct, uid, NULL);
+ purple_blist_add_buddy(bdy, NULL, grp, NULL);
+
+ purple_buddy_set_server_alias(bdy, user->name);
+ }
+
+ for (l = removed; l != NULL; l = l->next) {
+ bdy = purple_blist_find_buddy(acct, l->data);
+
+ if (bdy != NULL) {
+ purple_blist_remove_buddy(bdy);
+ }
+ }
+
+ fb_sync_contacts_add_timeout(fata);
+}
+
+static void
fb_cb_api_error(FbApi *api, GError *error, gpointer data)
{
FbData *fata = data;
@@ -963,6 +991,10 @@ fb_login(PurpleAccount *acct)
G_CALLBACK(fb_cb_api_contacts),
fata);
g_signal_connect(api,
+ "contacts-delta",
+ G_CALLBACK(fb_cb_api_contacts_delta),
+ fata);
+ g_signal_connect(api,
"error",
G_CALLBACK(fb_cb_api_error),
fata);
@@ -1486,7 +1518,7 @@ facebook_protocol_init(PurpleProtocol *protocol)
protocol->options = OPT_PROTO_CHAT_TOPIC;
opt = purple_account_option_int_new(_("Buddy list sync interval"),
- "sync-interval", 30);
+ "sync-interval", 5);
opts = g_list_prepend(opts, opt);
opt = purple_account_option_bool_new(_("Mark messages as read"),