diff options
author | dx <dx@dxzone.com.ar> | 2017-06-25 04:01:56 -0300 |
---|---|---|
committer | dx <dx@dxzone.com.ar> | 2017-06-25 04:01:56 -0300 |
commit | f7b1dcd720ded17ea70068260658d41962f21d80 (patch) | |
tree | 2d11e6d5a9b8b1d34108c2c1212ff5af0d4475e5 | |
parent | e744c6f2d68e4c89a525c81e19b15e2fc7b9a809 (diff) | |
download | pidgin-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.c | 155 | ||||
-rw-r--r-- | libpurple/protocols/facebook/facebook.c | 76 |
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"), |