diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | .travis.yml | 7 | ||||
-rw-r--r-- | README | 12 | ||||
-rw-r--r-- | configure.ac | 50 | ||||
-rw-r--r-- | debian/control | 4 | ||||
-rw-r--r-- | facebook/Makefile.am | 48 | ||||
-rw-r--r-- | facebook/facebook-api.c | 3585 | ||||
-rw-r--r-- | facebook/facebook-api.h | 979 | ||||
-rw-r--r-- | facebook/facebook-data.c | 404 | ||||
-rw-r--r-- | facebook/facebook-data.h | 257 | ||||
-rw-r--r-- | facebook/facebook-http.c | 1174 | ||||
-rw-r--r-- | facebook/facebook-http.h | 581 | ||||
-rw-r--r-- | facebook/facebook-id.h | 120 | ||||
-rw-r--r-- | facebook/facebook-json.c | 777 | ||||
-rw-r--r-- | facebook/facebook-json.h | 502 | ||||
-rw-r--r-- | facebook/facebook-mqtt.c | 1259 | ||||
-rw-r--r-- | facebook/facebook-mqtt.h | 724 | ||||
-rw-r--r-- | facebook/facebook-thrift.c | 1005 | ||||
-rw-r--r-- | facebook/facebook-thrift.h | 617 | ||||
-rw-r--r-- | facebook/facebook-util.c | 383 | ||||
-rw-r--r-- | facebook/facebook-util.h | 249 | ||||
-rw-r--r-- | facebook/facebook.c | 1274 | ||||
-rw-r--r-- | facebook/facebook.h | 54 | ||||
-rw-r--r-- | facebook/marshaller.list | 6 | ||||
-rw-r--r-- | valgrind.supp | 39 |
25 files changed, 9339 insertions, 4772 deletions
@@ -11,6 +11,7 @@ config.log config.status configure debian +facebook/facebook-marshal.* INSTALL libtool libtool.m4 diff --git a/.travis.yml b/.travis.yml index 3c89452..75f8c42 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,16 +9,13 @@ os: compiler: - gcc -env: - - DEBUG=disable - - DEBUG=enable - before_install: - sudo apt-get update -qq - sudo apt-get install -qq --no-install-recommends asciidoc clang + libjson-glib-dev lynx xsltproc xmlto @@ -43,7 +40,7 @@ before_install: - cd - script: - - ./autogen.sh --${DEBUG}-debug + - CFLAGS="-Werror" ./autogen.sh --enable-warnings - scan-build -k --use-cc=$(which "${CC}") --status-bugs @@ -61,19 +61,13 @@ Group Chats (creating chat): ## Debugging -Before debugging can begin, the plugin must be compiled with debugging -support. Once debugging support has been enabled, one of the two -supported environment variables can be defined to enable debugging -output. This can be used in unison with debuggers such as GDB, which -should enable easier tracing of bugs. +One of the two supported environment variables can be defined to enable +debugging output. This can be used in unison with debuggers such as +GDB, which should enable easier tracing of bugs. When posting to the issue tracker, please ensure any sensitive information has been stripped. -Enable debugging support (modify the build instructions above): - - $ ./autogen.sh --enable-debug - Enable debugging output: For bitlbee and the plugin: diff --git a/configure.ac b/configure.ac index c102a0b..c4a83a4 100644 --- a/configure.ac +++ b/configure.ac @@ -44,32 +44,27 @@ m4_define_default( ) AC_ARG_ENABLE( - [debug], + [warnings], [AS_HELP_STRING( - [--enable-debug], - [Enable debugging features] + [--enable-warnings], + [Enable additional compile-time (GCC) warnings] )], - [DEBUG="yes"], - [DEBUG="no"] -) - -AC_ARG_ENABLE( - [minimal-flags], - [AS_HELP_STRING( - [--enable-minimal-flags], - [Disable internal CFLAGS which are not required] - )], - [MINIMAL_FLAGS="yes"], - [MINIMAL_FLAGS="no"] + [WARNINGS="yes"], + [WARNINGS="no"] ) AS_IF( - [test "x$DEBUG" == "xyes"], - [AC_DEFINE(DEBUG_FACEBOOK, 1) - AS_IF( - [test "x$MINIMAL_FLAGS" == "xno"], - [CFLAGS="$CFLAGS -Wall -Wformat-nonliteral -g -O0"] - )] + [test "x$WARNINGS" == "xyes"], + [CFLAGS="$CFLAGS -Wall -Wextra \ + -Waggregate-return \ + -Wdeclaration-after-statement \ + -Wfloat-equal \ + -Wformat \ + -Winit-self \ + -Wmissing-declarations \ + -Wmissing-prototypes \ + -Wno-unused-parameter \ + -Wpointer-arith"] ) AC_ARG_WITH( @@ -81,9 +76,16 @@ AC_ARG_WITH( [plugindir="$with_plugindir"] ) -PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.32.0]) -PKG_CHECK_MODULES([BITLBEE], [bitlbee >= 3.2.2]) -PKG_CHECK_MODULES([ZLIB], [zlib]) +PKG_CHECK_MODULES([BITLBEE], [bitlbee >= 3.2.2]) +PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.32.0 gobject-2.0]) +PKG_CHECK_MODULES([JSON], [json-glib-1.0 >= 0.14.0]) +PKG_CHECK_MODULES([ZLIB], [zlib]) + +PKG_CHECK_VAR([GLIB_GENMARSHAL], [glib-2.0], [glib_genmarshal]) +AS_IF( + [test -z "$GLIB_GENMARSHAL"], + [AC_MSG_ERROR([The `glib-genmarshal' tool is missing.])] +) AS_IF( [test -z "$plugindir"], diff --git a/debian/control b/debian/control index 617a4a0..2bd6fa5 100644 --- a/debian/control +++ b/debian/control @@ -3,14 +3,14 @@ Maintainer: jgeboski <jgeboski@gmail.com> Section: misc Priority: optional Standards-Version: 3.9.6 -Build-Depends: debhelper (>= 9), dh-autoreconf, libglib2.0-dev (>= 2.32), bitlbee-dev (>= 3.2.2), zlib1g-dev +Build-Depends: bitlbee-dev (>= 3.2.2), debhelper (>= 9), dh-autoreconf, libglib2.0-dev (>= 2.32), libjson-glib-dev (>= 0.14), zlib1g-dev Homepage: https://github.com/jgeboski/bitlbee-facebook Package: bitlbee-facebook Architecture: any Section: misc Priority: optional -Depends: ${shlibs:Depends}, ${misc:Depends}, bitlbee (>= 3.2.2) | bitlbee-libpurple (>= 3.2.2) +Depends: ${shlibs:Depends}, ${misc:Depends}, bitlbee (>= 3.2.2) | bitlbee-libpurple (>= 3.2.2), libglib2.0-0 (>= 2.32), libjson-glib-1.0-0 (>= 0.14), zlib1g Homepage: https://github.com/jgeboski/bitlbee-facebook Description: Facebook protocol plugin for BitlBee BitlBee Facebook implements the Facebook Messenger protocol into diff --git a/facebook/Makefile.am b/facebook/Makefile.am index ca45223..0e77b91 100644 --- a/facebook/Makefile.am +++ b/facebook/Makefile.am @@ -1,23 +1,51 @@ -libdir = $(plugindir) -lib_LTLIBRARIES = facebook.la +libdir = $(plugindir) +lib_LTLIBRARIES = facebook.la + +facebook_la_CFLAGS = \ + $(BITLBEE_CFLAGS) \ + $(JSON_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(ZLIB_CFLAGS) + +facebook_la_LDFLAGS = \ + $(BITLBEE_LIBS) \ + $(JSON_LIBS) \ + $(GLIB_LIBS) \ + $(ZLIB_LIBS) -facebook_la_CFLAGS = $(BITLBEE_CFLAGS) $(GLIB_CFLAGS) $(ZLIB_CFLAGS) -facebook_la_LDFLAGS = $(BITLBEE_LIBS) $(GLIB_LIBS) $(ZLIB_LIBS) facebook_la_SOURCES = \ + facebook-marshal.c \ + facebook-marshal.h \ facebook.c \ facebook-api.c \ + facebook-api.h \ + facebook-data.c \ + facebook-data.h \ facebook-http.c \ - facebook-json.c \ - facebook-mqtt.c \ - facebook-thrift.c \ - facebook-util.c \ - facebook.h \ - facebook-api.h facebook-http.h \ + facebook-id.h \ + facebook-json.c \ facebook-json.h \ + facebook-mqtt.c \ facebook-mqtt.h \ + facebook-thrift.c \ facebook-thrift.h \ + facebook-util.c \ facebook-util.h # Build the library as a module facebook_la_LDFLAGS += -module -avoid-version + +EXTRA_DIST = \ + marshaller.list + +CLEANFILES = \ + facebook-marshal.c \ + facebook-marshal.h + +facebook-marshal.c: $(srcdir)/marshaller.list facebook-marshal.h + $(AM_V_GEN)echo "#include \"facebook-marshal.h\"" > $@ + $(AM_V_at)$(GLIB_GENMARSHAL) --prefix=fb_marshal --body $(srcdir)/marshaller.list >> $@ + +facebook-marshal.h: $(srcdir)/marshaller.list + $(AM_V_GEN)$(GLIB_GENMARSHAL) --prefix=fb_marshal --header $(srcdir)/marshaller.list > $@ diff --git a/facebook/facebook-api.c b/facebook/facebook-api.c index dfb4e9a..7d91b05 100644 --- a/facebook/facebook-api.c +++ b/facebook/facebook-api.c @@ -15,350 +15,969 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <bitlbee.h> #include <stdarg.h> #include <string.h> -#include <sha1.h> #include "facebook-api.h" +#include "facebook-http.h" +#include "facebook-json.h" +#include "facebook-marshal.h" #include "facebook-thrift.h" +#include "facebook-util.h" -/** - * Gets the error domain for #fb_api. - * - * @return The #GQuark of the error domain. - **/ -GQuark fb_api_error_quark(void) +typedef struct _FbApiData FbApiData; + +enum +{ + PROP_0, + + PROP_CID, + PROP_DID, + PROP_MID, + PROP_STOKEN, + PROP_TOKEN, + PROP_UID, + + PROP_N +}; + +struct _FbApiPrivate +{ + FbHttp *http; + FbMqtt *mqtt; + GHashTable *data; + + FbId uid; + gint64 sid; + guint64 mid; + gchar *cid; + gchar *did; + gchar *stoken; + gchar *token; + + GHashTable *mids; + gboolean invisible; + guint unread; + +}; + +struct _FbApiData +{ + gpointer data; + GDestroyNotify func; +}; + +static void +fb_api_attach(FbApi *api, FbId aid, const gchar *msgid, FbApiMessage *msg); + +static void +fb_api_contacts_after(FbApi *api, const gchar *writeid); + +static void +fb_api_sticker(FbApi *api, FbId sid, FbApiMessage *msg); + +G_DEFINE_TYPE(FbApi, fb_api, G_TYPE_OBJECT); + +static void +fb_api_set_property(GObject *obj, guint prop, const GValue *val, + GParamSpec *pspec) +{ + FbApiPrivate *priv = FB_API(obj)->priv; + + switch (prop) { + case PROP_CID: + g_free(priv->cid); + priv->cid = g_value_dup_string(val); + break; + case PROP_DID: + g_free(priv->did); + priv->did = g_value_dup_string(val); + break; + case PROP_MID: + priv->mid = g_value_get_uint64(val); + break; + case PROP_STOKEN: + g_free(priv->stoken); + priv->stoken = g_value_dup_string(val); + break; + case PROP_TOKEN: + g_free(priv->token); + priv->token = g_value_dup_string(val); + break; + case PROP_UID: + priv->uid = g_value_get_int64(val); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop, pspec); + break; + } +} + +static void +fb_api_get_property(GObject *obj, guint prop, GValue *val, GParamSpec *pspec) +{ + FbApiPrivate *priv = FB_API(obj)->priv; + + switch (prop) { + case PROP_CID: + g_value_set_string(val, priv->cid); + break; + case PROP_DID: + g_value_set_string(val, priv->did); + break; + case PROP_MID: + g_value_set_uint64(val, priv->mid); + break; + case PROP_STOKEN: + g_value_set_string(val, priv->stoken); + break; + case PROP_TOKEN: + g_value_set_string(val, priv->token); + break; + case PROP_UID: + g_value_set_int64(val, priv->uid); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop, pspec); + break; + } +} + + +static void +fb_api_dispose(GObject *obj) +{ + FbApiData *fata; + FbApiPrivate *priv = FB_API(obj)->priv; + GHashTableIter iter; + + g_hash_table_iter_init(&iter, priv->data); + + while (g_hash_table_iter_next(&iter, NULL, (gpointer) &fata)) { + fata->func(fata->data); + g_free(fata); + } + + g_object_unref(priv->http); + g_object_unref(priv->mqtt); + + g_hash_table_destroy(priv->data); + g_hash_table_destroy(priv->mids); + + g_free(priv->cid); + g_free(priv->did); + g_free(priv->stoken); + g_free(priv->token); +} + +static void +fb_api_class_init(FbApiClass *klass) { - static GQuark q; + GObjectClass *gklass = G_OBJECT_CLASS(klass); + GParamSpec *props[PROP_N] = {NULL}; + + gklass->set_property = fb_api_set_property; + gklass->get_property = fb_api_get_property; + gklass->dispose = fb_api_dispose; + g_type_class_add_private(klass, sizeof (FbApiPrivate)); + + /** + * FbApi:cid: + * + * The client identifier for MQTT. This value should be saved + * and loaded for persistence. + */ + props[PROP_CID] = g_param_spec_string( + "cid", + "Client ID", + "Client identifier for MQTT", + NULL, + G_PARAM_READWRITE); + + /** + * FbApi:did: + * + * The device identifier for the MQTT message queue. This value + * should be saved and loaded for persistence. + */ + props[PROP_DID] = g_param_spec_string( + "did", + "Device ID", + "Device identifier for the MQTT message queue", + NULL, + G_PARAM_READWRITE); + + /** + * FbApi:mid: + * + * The MQTT identifier. This value should be saved and loaded + * for persistence. + */ + props[PROP_MID] = g_param_spec_uint64( + "mid", + "MQTT ID", + "MQTT identifier", + 0, G_MAXUINT64, 0, + G_PARAM_READWRITE); + + /** + * FbApi:stoken: + * + * The synchronization token for the MQTT message queue. This + * value should be saved and loaded for persistence. + */ + props[PROP_STOKEN] = g_param_spec_string( + "stoken", + "Sync Token", + "Synchronization token for the MQTT message queue", + NULL, + G_PARAM_READWRITE); + + /** + * FbApi:token: + * + * The access token for authentication. This value should be + * saved and loaded for persistence. + */ + props[PROP_TOKEN] = g_param_spec_string( + "token", + "Access Token", + "Access token for authentication", + NULL, + G_PARAM_READWRITE); + + /** + * FbApi:uid: + * + * The #FbId of the user of the #FbApi. + */ + props[PROP_UID] = g_param_spec_int64( + "uid", + "User ID", + "User identifier", + 0, G_MAXINT64, 0, + G_PARAM_READWRITE); + g_object_class_install_properties(gklass, PROP_N, props); + + /** + * FbApi::auth: + * @api: The #FbApi. + * + * Emitted upon the successful completion of the authentication + * process. This is emitted as a result of #fb_api_auth(). + */ + g_signal_new("auth", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + /** + * FbApi::connect: + * @api: The #FbApi. + * + * Emitted upon the successful completion of the connection + * process. This is emitted as a result of #fb_api_connect(). + */ + g_signal_new("connect", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + /** + * FbApi::contact: + * @api: The #FbApi. + * @user: The #FbApiUser. + * + * Emitted upon the successful reply of a contact request. This + * is emitted as a result of #fb_api_contact(). + */ + g_signal_new("contact", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * FbApi::contacts: + * @api: The #FbApi. + * @users: The #GSList of #FbApiUser's. + * @complete: #TRUE if the list is fetched, otherwise #FALSE. + * + * Emitted upon the successful reply of a contacts request. + * This is emitted as a result of #fb_api_contacts(). This can + * be emitted multiple times before the entire contacts list + * has been fetched. Use @complete for detecting the completion + * status of the list fetch. + */ + g_signal_new("contacts", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER_BOOLEAN, + G_TYPE_NONE, + 2, G_TYPE_POINTER, G_TYPE_BOOLEAN); + + /** + * FbApi::error: + * @api: The #FbApi. + * @error: The #GError. + * + * Emitted whenever an error is hit within the #FbApi. This + * should disconnect the #FbApi with #fb_api_disconnect(). + */ + g_signal_new("error", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, G_TYPE_ERROR); + + /** + * FbApi::events: + * @api: The #FbApi. + * @events: The #GSList of #FbApiEvent's. + * + * Emitted upon incoming events from the stream. + */ + g_signal_new("events", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * FbApi::messages: + * @api: The #FbApi. + * @msgs: The #GSList of #FbApiMessage's. + * + * Emitted upon incoming messages from the stream. + */ + g_signal_new("messages", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * FbApi::presences: + * @api: The #FbApi. + * @press: The #GSList of #FbApiPresence's. + * + * Emitted upon incoming presences from the stream. + */ + g_signal_new("presences", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * FbApi::thread: + * @api: The #FbApi. + * @thrd: The #FbApiThread. + * + * Emitted upon the successful reply of a thread request. This + * is emitted as a result of #fb_api_thread(). + */ + g_signal_new("thread", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * FbApi::thread-create: + * @api: The #FbApi. + * @tid: The thread #FbId. + * + * Emitted upon the successful reply of a thread creation + * request. This is emitted as a result of + * #fb_api_thread_create(). + */ + g_signal_new("thread-create", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__INT64, + G_TYPE_NONE, + 1, FB_TYPE_ID); + + /** + * FbApi::threads: + * @api: The #FbApi. + * @thrds: The #GSList of #FbApiThread's. + * + * Emitted upon the successful reply of a threads request. This + * is emitted as a result of #fb_api_threads(). + */ + g_signal_new("threads", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + /** + * FbApi::typing: + * @api: The #FbApi. + * @typg: The #FbApiTyping. + * + * Emitted upon an incoming typing state from the stream. + */ + g_signal_new("typing", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); +} + +static void +fb_api_init(FbApi *api) +{ + FbApiPrivate *priv; - if (G_UNLIKELY(q == 0)) + priv = G_TYPE_INSTANCE_GET_PRIVATE(api, FB_TYPE_API, FbApiPrivate); + api->priv = priv; + + priv->http = fb_http_new(NULL); + priv->mqtt = fb_mqtt_new(); + priv->data = g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, NULL); + priv->mids = g_hash_table_new_full(g_int64_hash, g_int64_equal, + g_free, NULL); +} + +GQuark +fb_api_error_quark(void) +{ + static GQuark q = 0; + + if (G_UNLIKELY(q == 0)) { q = g_quark_from_static_string("fb-api-error-quark"); + } return q; } -/** - * Creates a new #json_value for JSON contents of the #fb_api. This - * function is special in that it handles all errors, unlike the parent - * function #fb_json_new(). The returned #json_value should be freed - * with #json_value_free() when no longer needed. - * - * @param api The #fb_api. - * @param data The data. - * @param size The size of the data. - * @param json The return location for the json_value. - * - * @return TRUE if the data was parsed without error, otherwise FALSE. - **/ -static gboolean fb_api_json_new(fb_api_t *api, const gchar *data, gsize size, - json_value **json) +static void +fb_api_data_set(FbApi *api, gpointer handle, gpointer data, + GDestroyNotify func) { - json_value *jv; - const gchar *msg; - gint64 code; + FbApiData *fata; + FbApiPrivate *priv = api->priv; - jv = fb_json_new(data, size, &api->err); + fata = g_new0(FbApiData, 1); + fata->data = data; + fata->func = func; + g_hash_table_replace(priv->data, handle, fata); +} - if (G_UNLIKELY(api->err != NULL)) { - fb_api_error(api, 0, NULL); - return FALSE; +static gpointer +fb_api_data_take(FbApi *api, gconstpointer handle) +{ + FbApiData *fata; + FbApiPrivate *priv = api->priv; + gpointer data; + + fata = g_hash_table_lookup(priv->data, handle); + + if (fata == NULL) { + return NULL; } - if (fb_json_int_chk(jv, "error_code", &code)) { - if (!fb_json_str_chk(jv, "error_msg", &msg)) - msg = "Generic Error"; + data = fata->data; + g_hash_table_remove(priv->data, handle); + g_free(fata); + return data; +} + +static gboolean +fb_api_json_chk(FbApi *api, gconstpointer data, gssize size, JsonNode **node) +{ + const gchar *str; + FbApiError errc = FB_API_ERROR_GENERAL; + FbApiPrivate *priv; + FbJsonValues *values; + gboolean success = TRUE; + gchar *msg; + GError *err = NULL; + gint64 code; + guint i; + JsonNode *root; + + static const gchar *exprs[] = { + "$.error.message", + "$.error.summary", + "$.error_msg", + "$.errorCode", + "$.failedSend.errorMessage", + }; + + g_return_val_if_fail(FB_IS_API(api), FALSE); + priv = api->priv; - fb_api_error(api, FB_API_ERROR_GENERAL, "%s", msg); - json_value_free(jv); + if (G_UNLIKELY(size == 0)) { + fb_api_error(api, FB_API_ERROR_GENERAL, "Empty JSON data"); return FALSE; - } else if (fb_json_str_chk(jv, "errorCode", &msg)) { - if ((g_ascii_strcasecmp(msg, "ERROR_QUEUE_NOT_FOUND") == 0) || - (g_ascii_strcasecmp(msg, "ERROR_QUEUE_LOST") == 0)) - { - g_free(api->stoken); - api->stoken = NULL; + } + + fb_util_debug_info("Parsing JSON: %.*s", (gint) size, + (const gchar *) data); + root = fb_json_node_new(data, size, &err); + FB_API_ERROR_EMIT(api, err, return FALSE); + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, "$.error_code"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.error.type"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.errorCode"); + fb_json_values_update(values, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return FALSE + ); + + code = fb_json_values_next_int(values, 0); + str = fb_json_values_next_str(values, NULL); + + if ((g_strcmp0(str, "OAuthException") == 0) || (code == 401)) { + errc = FB_API_ERROR_AUTH; + success = FALSE; + + g_free(priv->stoken); + priv->stoken = NULL; + + g_free(priv->token); + priv->token = NULL; + } + + str = fb_json_values_next_str(values, NULL); + + if ((g_strcmp0(str, "ERROR_QUEUE_NOT_FOUND") == 0) || + (g_strcmp0(str, "ERROR_QUEUE_LOST") == 0)) + { + errc = FB_API_ERROR_QUEUE; + success = FALSE; + + g_free(priv->stoken); + priv->stoken = NULL; + } + + g_object_unref(values); + + for (msg = NULL, i = 0; i < G_N_ELEMENTS(exprs); i++) { + msg = fb_json_node_get_str(root, exprs[i], NULL); + + if (msg != NULL) { + success = FALSE; + break; } + } - fb_api_error(api, FB_API_ERROR_GENERAL, "%s", msg); - json_value_free(jv); + if (!success && (msg == NULL)) { + msg = g_strdup("Unknown error"); + } + + if (msg != NULL) { + fb_api_error(api, errc, "%s", msg); + json_node_free(root); + g_free(msg); return FALSE; } - *json = jv; + if (node != NULL) { + *node = root; + } else { + json_node_free(root); + } + return TRUE; } -/** - * Checks an #fb_http_req for errors. - * - * @param api The #fb_api. - * @param req The #fb_http_req. - * - * @return TRUE if no errors were found, otherwise FALSE. - **/ -static gboolean fb_api_http_chk(fb_api_t *api, fb_http_req_t *req) +static gboolean +fb_api_http_chk(FbApi *api, FbHttpRequest *req, JsonNode **root) { - if (G_UNLIKELY(api->err != NULL)) { - fb_api_error(api, 0, NULL); - return FALSE; + const gchar *data; + GError *err; + gsize size; + + data = fb_http_request_get_data(req, &size); + err = fb_http_request_take_error(req); + + if ((err == NULL) && (root == NULL)) { + return TRUE; } - if (req->err != NULL) { - api->err = g_error_copy(req->err); - fb_api_error(api, 0, NULL); - return FALSE; + /* Rudimentary check to prevent wrongful error parsing */ + if ((size < 2) || (data[0] != '{') || (data[size - 1] != '}')) { + FB_API_ERROR_EMIT(api, err, return FALSE); } - return TRUE; -} + if (!fb_api_json_chk(api, data, size, root)) { + if (G_UNLIKELY(err != NULL)) { + g_error_free(err); + } -/** - * Creates a new #fb_http_req for a #fb_api request. - * - * @param api The #fb_api. - * @param host The host. - * @param path The path. - * @param func The #fb_http_func. - * @param class The class. - * @param name The friendly name. - * @param method The method. - **/ -static fb_http_req_t *fb_api_req_new(fb_api_t *api, const gchar *host, - const gchar *path, fb_http_func_t func, - const gchar *class, const gchar *name, - const gchar *method) -{ - fb_http_req_t *req; - - req = fb_http_req_new(api->http, host, 443, path, func, api); - req->flags = FB_HTTP_REQ_FLAG_POST | FB_HTTP_REQ_FLAG_SSL; - - fb_http_req_params_set(req, - FB_HTTP_PAIR("api_key", FB_API_KEY), - FB_HTTP_PAIR("fb_api_caller_class", class), - FB_HTTP_PAIR("fb_api_req_friendly_name", name), - FB_HTTP_PAIR("method", method), - FB_HTTP_PAIR("client_country_code", "US"), - FB_HTTP_PAIR("format", "json"), - FB_HTTP_PAIR("locale", "en_US"), - NULL - ); + return FALSE; + } - return req; + FB_API_ERROR_EMIT(api, err, return FALSE); + return TRUE; } -/** - * Sends a #fb_http_req for a #fb_api. This computes the signature for - * the request and sets the "sig" parameter. This sets the OAuth header - * for authorization. - * - * @param api The #fb_api. - * @param req The #fb_http_req. - **/ -static void fb_api_req_send(fb_api_t *api, fb_http_req_t *req) -{ - GString *gstr; - GList *keys; - GList *l; +static FbHttpRequest * +fb_api_http_req(FbApi *api, const gchar *url, const gchar *name, + const gchar *method, FbHttpValues *values, + FbHttpFunc func) +{ const gchar *key; const gchar *val; - gchar *hash; - gchar *auth; - - /* Ensure an old signature is not computed */ - g_hash_table_remove(req->params, "sig"); - - gstr = g_string_sized_new(128); - keys = g_hash_table_get_keys(req->params); + FbApiPrivate *priv = api->priv; + FbHttpRequest *req; + FbHttpValues *hdrs; + FbHttpValues *prms; + gchar *data; + GList *keys; + GList *l; + GString *gstr; + + fb_http_values_set_str(values, "api_key", FB_API_KEY); + fb_http_values_set_str(values, "device_id", priv->did); + fb_http_values_set_str(values, "fb_api_req_friendly_name", name); + fb_http_values_set_str(values, "format", "json"); + fb_http_values_set_str(values, "method", method); + + data = fb_util_locale_str(); + fb_http_values_set_str(values, "locale", data); + g_free(data); + + req = fb_http_request_new(priv->http, url, TRUE, func, api); + fb_http_values_remove(values, "sig"); + + gstr = g_string_new(NULL); + keys = fb_http_values_get_keys(values); keys = g_list_sort(keys, (GCompareFunc) g_ascii_strcasecmp); for (l = keys; l != NULL; l = l->next) { key = l->data; - val = g_hash_table_lookup(req->params, key); + val = fb_http_values_get_str(values, key, NULL); g_string_append_printf(gstr, "%s=%s", key, val); } g_string_append(gstr, FB_API_SECRET); - hash = g_compute_checksum_for_string(G_CHECKSUM_MD5, gstr->str, gstr->len); + data = g_compute_checksum_for_string(G_CHECKSUM_MD5, gstr->str, gstr->len); + fb_http_values_set_str(values, "sig", data); - fb_http_req_params_set(req, - FB_HTTP_PAIR("sig", hash), - NULL - ); - - g_free(hash); - g_list_free(keys); g_string_free(gstr, TRUE); + g_list_free(keys); + g_free(data); - if (api->token != NULL) { - auth = g_strdup_printf("OAuth %s", api->token); - fb_http_req_headers_set(req, - FB_HTTP_PAIR("Authorization", auth), - NULL - ); - g_free(auth); + if (priv->token != NULL) { + hdrs = fb_http_request_get_headers(req); + fb_http_values_set_strf(hdrs, "Authorization", "OAuth %s", priv->token); } - fb_http_req_send(req); + prms = fb_http_request_get_params(req); + fb_http_values_consume(prms, values); + fb_http_request_send(req); + return req; } -/** - * Implemented #fb_http_func for simple boolean error checking. - * - * @param req The #fb_http_req. - * @param data The user-defined data, which is #fb_api. - **/ -static void fb_api_cb_http_bool(fb_http_req_t *req, gpointer data) +static FbHttpRequest * +fb_api_http_query(FbApi *api, gint64 query, JsonBuilder *builder, + FbHttpFunc func) { - fb_api_t *api = data; + const gchar *name; + FbHttpValues *prms; + gchar *json; + + switch (query) { + case FB_API_QUERY_CONTACT: + name = "FetchContactQuery"; + break; + case FB_API_QUERY_CONTACTS: + name = "FetchContactsFullQuery"; + break; + case FB_API_QUERY_CONTACTS_AFTER: + name = "FetchContactsFullWithAfterQuery"; + break; + case FB_API_QUERY_STICKER: + name = "FetchStickersWithPreviewsQuery"; + break; + case FB_API_QUERY_THREAD: + name = "ThreadQuery"; + break; + case FB_API_QUERY_THREADS: + name = "ThreadListQuery"; + break; + case FB_API_QUERY_XMA: + name = "XMAQuery"; + break; + default: + g_return_val_if_reached(NULL); + return NULL; + } + + prms = fb_http_values_new(); + json = fb_json_bldr_close(builder, JSON_NODE_OBJECT, NULL); + fb_http_values_set_strf(prms, "query_id", "%" G_GINT64_FORMAT, query); + fb_http_values_set_str(prms, "query_params", json); + g_free(json); - if (!fb_api_http_chk(api, req)) + return fb_api_http_req(api, FB_API_URL_GQL, name, "get", prms, func); +} + +static void +fb_api_cb_http_bool(FbHttpRequest *req, gpointer data) +{ + const gchar *hata; + FbApi *api = data; + + if (!fb_api_http_chk(api, req, NULL)) { return; + } - if (bool2int(req->body) == 0) + hata = fb_http_request_get_data(req, NULL); + + if (bool2int((gchar *) hata)) { fb_api_error(api, FB_API_ERROR, "Failed generic API operation"); + } } +static void +fb_api_cb_mqtt_error(FbMqtt *mqtt, GError *error, gpointer data) +{ + FbApi *api = data; + g_signal_emit_by_name(api, "error", error); +} -/** - * Implements #fb_mqtt_funcs->error(). - * - * @param mqtt The #fb_mqtt. - * @param err The #GError. - * @param data The user-defined data, which is #fb_api. - **/ -static void fb_api_cb_mqtt_error(fb_mqtt_t *mqtt, GError *err, gpointer data) +static void +fb_api_cb_mqtt_open(FbMqtt *mqtt, gpointer data) { - fb_api_t *api = data; + const GByteArray *bytes; + FbApi *api = data; + FbApiPrivate *priv = api->priv; + FbThrift *thft; + GByteArray *cytes; - if (api->err == NULL) { - api->err = g_error_copy(err); - fb_api_error(api, 0, NULL); - } -} + static guint8 flags = FB_MQTT_CONNECT_FLAG_USER | + FB_MQTT_CONNECT_FLAG_PASS | + FB_MQTT_CONNECT_FLAG_CLR; -/** - * Implements #fb_mqtt_funcs->open(). - * - * @param mqtt The #fb_mqtt. - * @param data The user-defined data, which is #fb_api. - **/ -static void fb_api_cb_mqtt_open(fb_mqtt_t *mqtt, gpointer data) -{ - fb_api_t *api = data; - gchar *msg; - - static guint8 flags = - FB_MQTT_CONNECT_FLAG_USER | - FB_MQTT_CONNECT_FLAG_PASS | - FB_MQTT_CONNECT_FLAG_CLR; - - msg = g_strdup_printf("{" - "\"u\":\"%" FB_ID_FORMAT "\"," - "\"a\":\"" FB_API_AGENT "\"," - "\"mqtt_sid\":%s," - "\"d\":\"%s\"," - "\"chat_on\":true," - "\"no_auto_fg\":true," - "\"fg\":false," - "\"pf\":\"jz\"," - "\"nwt\":1," - "\"nwst\":0" - "}", api->uid, api->mid, api->cuid); - - fb_mqtt_connect(mqtt, - flags, /* Flags */ - api->cid, /* Client identifier */ - msg, /* Will message */ - api->token, /* Username */ - NULL); - - g_free(msg); -} - -/** - * Implemented #fb_http_func for the sequence identifier. - * - * @param req The #fb_http_req. - * @param data The user-defined data, which is #fb_api. - **/ -static void fb_api_cb_seqid(fb_http_req_t *req, gpointer data) -{ - fb_api_t *api = data; - json_value *json; - json_value *jv; - const gchar *str; + thft = fb_thrift_new(NULL, 0); - if (!fb_api_http_chk(api, req) || - !fb_api_json_new(api, req->body, req->body_size, &json)) - { - return; - } + /* Write the client identifier */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 1); + fb_thrift_write_str(thft, priv->cid); - /* Scattered values lead to a gnarly conditional... */ - if (!fb_json_val_chk(json, "data", json_array, &jv) || + fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRUCT, 4); - /* Obtain the first array element */ - (jv->u.array.length != 1) || - ((jv = jv->u.array.values[0]) == NULL) || + /* Write the user identifier */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 5); + fb_thrift_write_i64(thft, priv->uid); - /* Check the name */ - !fb_json_str_chk(jv, "name", &str) || - (g_ascii_strcasecmp(str, "thread_list_ids") != 0) || + /* Write the information string */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 6); + fb_thrift_write_str(thft, ""); - /* Obtain the sequence identifier */ - !fb_json_val_chk(jv, "fql_result_set", json_array, &jv) || - (jv->u.array.length != 1) || - !fb_json_str_chk(jv->u.array.values[0], "sync_sequence_id", &str)) - { - fb_api_error(api, FB_API_ERROR, "Failed to obtain SequenceID"); - goto finish; + /* Write the UNKNOWN ("cp"?) */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 7); + fb_thrift_write_i64(thft, 23); + + /* Write the UNKNOWN ("ecp"?) */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 8); + fb_thrift_write_i64(thft, 26); + + /* Write the UNKNOWN */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_I32, 9); + fb_thrift_write_i32(thft, 1); + + /* Write the UNKNOWN ("no_auto_fg"?) */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_BOOL, 10); + fb_thrift_write_bool(thft, TRUE); + + /* Write the visibility state */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_BOOL, 11); + fb_thrift_write_bool(thft, !priv->invisible); + + /* Write the device identifier */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 12); + fb_thrift_write_str(thft, priv->did); + + /* Write the UNKNOWN ("fg"?) */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_BOOL, 13); + fb_thrift_write_bool(thft, TRUE); + + /* Write the UNKNOWN ("nwt"?) */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_I32, 14); + fb_thrift_write_i32(thft, 1); + + /* Write the UNKNOWN ("nwst"?) */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_I32, 15); + fb_thrift_write_i32(thft, 0); + + /* Write the MQTT identifier */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 16); + fb_thrift_write_i64(thft, priv->mid); + + /* Write the UNKNOWN */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_LIST, 18); + fb_thrift_write_list(thft, FB_THRIFT_TYPE_I32, 0); + fb_thrift_write_stop(thft); + + /* Write the token */ + fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 19); + fb_thrift_write_str(thft, priv->token); + + /* Write the STOP for the struct */ + fb_thrift_write_stop(thft); + + bytes = fb_thrift_get_bytes(thft); + cytes = fb_util_zcompress(bytes); + + fb_util_debug_hexdump(FB_UTIL_DEBUG_LEVEL_INFO, bytes, "Writing connect"); + fb_mqtt_connect(mqtt, flags, cytes); + + g_byte_array_free(cytes, TRUE); + g_object_unref(thft); +} + +static void +fb_api_connect_queue(FbApi *api) +{ + FbApiPrivate *priv = api->priv; + gchar *json; + JsonBuilder *bldr; + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_int(bldr, "delta_batch_size", 125); + fb_json_bldr_add_int(bldr, "max_deltas_able_to_process", 1250); + fb_json_bldr_add_int(bldr, "sync_api_version", 3); + fb_json_bldr_add_str(bldr, "encoding", "JSON"); + + if (priv->stoken == NULL) { + fb_json_bldr_add_int(bldr, "initial_titan_sequence_id", priv->sid); + fb_json_bldr_add_str(bldr, "device_id", priv->did); + fb_json_bldr_add_int(bldr, "entity_fbid", priv->uid); + + fb_json_bldr_obj_begin(bldr, "queue_params"); + fb_json_bldr_add_str(bldr, "buzz_on_deltas_enabled", "false"); + + fb_json_bldr_obj_begin(bldr, "graphql_query_hashes"); + fb_json_bldr_add_str(bldr, "xma_query_id", + G_STRINGIFY(FB_API_QUERY_XMA)); + fb_json_bldr_obj_end(bldr); + + fb_json_bldr_obj_begin(bldr, "graphql_query_params"); + fb_json_bldr_obj_begin(bldr, G_STRINGIFY(FB_API_QUERY_XMA)); + fb_json_bldr_add_str(bldr, "xma_id", "<ID>"); + fb_json_bldr_obj_end(bldr); + fb_json_bldr_obj_end(bldr); + fb_json_bldr_obj_end(bldr); + + json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); + fb_api_publish(api, "/messenger_sync_create_queue", "%s", json); + g_free(json); + return; } - if (api->stoken == NULL) { - fb_api_publish(api, "/messenger_sync_create_queue", "{" - "\"device_params\":{}," - "\"encoding\":\"JSON\"," - "\"max_deltas_able_to_process\":1250," - "\"initial_titan_sequence_id\":%s," - "\"sync_api_version\":2," - "\"delta_batch_size\":125," - "\"device_id\":\"%s\"" - "}", str, api->cuid); + fb_json_bldr_add_int(bldr, "last_seq_id", priv->sid); + fb_json_bldr_add_str(bldr, "sync_token", priv->stoken); + + json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); + fb_api_publish(api, "/messenger_sync_get_diffs", "%s", json); + g_signal_emit_by_name(api, "connect"); + g_free(json); + +} - goto finish; +static void +fb_api_cb_seqid(FbHttpRequest *req, gpointer data) +{ + const gchar *str; + FbApi *api = data; + FbApiPrivate *priv = api->priv; + FbJsonValues *values; + GError *err = NULL; + JsonNode *root; + + if (!fb_api_http_chk(api, req, &root)) { + return; } - fb_api_publish(api, "/messenger_sync_get_diffs", "{" - "\"encoding\":\"JSON\"," - "\"last_seq_id\":%s," - "\"max_deltas_able_to_process\":1250," - "\"sync_api_version\":2," - "\"sync_token\":\"%s\"," - "\"delta_batch_size\":125" - "}", str, api->stoken); + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, + "$.viewer.message_threads.sync_sequence_id"); + fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, + "$.viewer.message_threads.unread_count"); + fb_json_values_update(values, &err); - FB_API_FUNC(api, connect); + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); + + str = fb_json_values_next_str(values, "0"); + priv->sid = g_ascii_strtoll(str, NULL, 10); + priv->unread = fb_json_values_next_int(values, 0); -finish: - json_value_free(json); + fb_api_connect_queue(api); + g_object_unref(values); + json_node_free(root); } -/** - * Implements #fb_mqtt_funcs->connack(). - * - * @param mqtt The #fb_mqtt. - * @param data The user-defined data, which is #fb_api. - **/ -static void fb_api_cb_mqtt_connack(fb_mqtt_t *mqtt, gpointer data) +static void +fb_api_cb_mqtt_connect(FbMqtt *mqtt, gpointer data) { - fb_api_t *api = data; - fb_http_req_t *req; + FbApi *api = data; + FbApiPrivate *priv = api->priv; + gchar *json; + JsonBuilder *bldr; + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_bool(bldr, "foreground", TRUE); + fb_json_bldr_add_int(bldr, "keepalive_timeout", FB_MQTT_KA); - fb_api_publish(api, "/foreground_state", "{" - "\"foreground\": true," - "\"keepalive_timeout\": %d" - "}", FB_MQTT_KA); + json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); + fb_api_publish(api, "/foreground_state", "%s", json); + g_free(json); fb_mqtt_subscribe(mqtt, "/inbox", 0, @@ -378,165 +997,438 @@ static void fb_api_cb_mqtt_connack(fb_mqtt_t *mqtt, gpointer data) /* Notifications seem to lead to some sort of sending rate limit */ fb_mqtt_unsubscribe(mqtt, "/orca_message_notifications", NULL); - req = fb_api_req_new(api, FB_API_GHOST, FB_API_PATH_FQL, - fb_api_cb_seqid, - "com.facebook.orca.protocol.methods.u", - "fetchThreadList", - "GET"); + if (priv->sid == 0) { + /* See fb_api_thread_list() for key mapping */ + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_str(bldr, "1", "0"); + fb_api_http_query(api, FB_API_QUERY_THREADS, bldr, fb_api_cb_seqid); + } else { + fb_api_connect_queue(api); + } +} + +static void +fb_api_cb_publish_mark(FbApi *api, GByteArray *pload) +{ + FbJsonValues *values; + GError *err = NULL; + JsonNode *root; + + if (!fb_api_json_chk(api, pload->data, pload->len, &root)) { + return; + } + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_BOOL, FALSE, "$.succeeded"); + fb_json_values_update(values, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); - static const gchar *query = "{" - "\"thread_list_ids\":\"" - "SELECT sync_sequence_id " - "FROM unified_thread " - "WHERE folder='inbox' " - "ORDER BY sync_sequence_id " - "DESC LIMIT 1\"" - "}"; + if (!fb_json_values_next_bool(values, TRUE)) { + fb_api_error(api, FB_API_ERROR_GENERAL, + "Failed to mark thread as read"); + } - fb_http_req_params_set(req, FB_HTTP_PAIR("q", query), NULL); - fb_api_req_send(api, req); + g_object_unref(values); + json_node_free(root); } -/** - * Handles typing notifications which are to be published to the user. - * - * @param api The #fb_api. - * @param pload The message payload. - **/ -static void fb_api_cb_publish_tn(fb_api_t *api, const GByteArray *pload) -{ - json_value *json; - fb_api_typing_t typg; - const gchar *str; - gint64 uid; - gint64 state; - - if (!fb_api_json_new(api, (gchar*) pload->data, pload->len, &json)) +static GSList * +fb_api_event_parse(FbApi *api, FbApiEvent *event, GSList *events, + JsonNode *root, GError **error) +{ + const gchar *str; + FbApiEvent *devent; + FbJsonValues *values; + GError *err = NULL; + guint i; + + static const struct { + FbApiEventType type; + const gchar *expr; + } evtypes[] = { + { + FB_API_EVENT_TYPE_THREAD_USER_ADDED, + "$.log_message_data.added_participants" + }, { + FB_API_EVENT_TYPE_THREAD_USER_REMOVED, + "$.log_message_data.removed_participants" + } + }; + + for (i = 0; i < G_N_ELEMENTS(evtypes); i++) { + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$"); + fb_json_values_set_array(values, FALSE, evtypes[i].expr); + + while (fb_json_values_update(values, &err)) { + str = fb_json_values_next_str(values, ""); + str = strrchr(str, ':'); + + if (str != NULL) { + devent = fb_api_event_dup(event); + devent->type = evtypes[i].type; + devent->uid = FB_ID_FROM_STR(str + 1); + events = g_slist_prepend(events, devent); + } + } + + g_object_unref(values); + + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + break; + } + } + + return events; +} + +static void +fb_api_cb_mercury(FbApi *api, GByteArray *pload) +{ + const gchar *str; + FbApiEvent event; + FbJsonValues *values; + GError *err = NULL; + GSList *events = NULL; + JsonNode *root; + JsonNode *node; + + if (!fb_api_json_chk(api, pload->data, pload->len, &root)) { return; + } - if (!fb_json_str_chk(json, "type", &str) || - (g_ascii_strcasecmp(str, "typ") != 0)) - { - goto finish; + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.thread_fbid"); + fb_json_values_set_array(values, FALSE, "$.actions"); + + while (fb_json_values_update(values, &err)) { + fb_api_event_reset(&event); + str = fb_json_values_next_str(values, "0"); + event.tid = FB_ID_FROM_STR(str); + + node = fb_json_values_get_root(values); + events = fb_api_event_parse(api, &event, events, node, &err); } - if (!fb_json_int_chk(json, "sender_fbid", &uid) || - !fb_json_int_chk(json, "state", &state)) - { - fb_api_error(api, FB_API_ERROR, "Failed to obtain typing state"); - goto finish; + if (G_LIKELY(err == NULL)) { + events = g_slist_reverse(events); + g_signal_emit_by_name(api, "events", events); + } else { + fb_api_error_emit(api, err); } - typg.uid = uid; - typg.state = state; - FB_API_FUNC(api, typing, &typg); + g_slist_free_full(events, (GDestroyNotify) fb_api_event_free); + g_object_unref(values); + json_node_free(root); -finish: - json_value_free(json); } -/** - * Handles messages which are to be published to the user. - * - * @param api The #fb_api. - * @param pload The message payload. - **/ -static void fb_api_cb_publish_ms(fb_api_t *api, const GByteArray *pload) -{ - GSList *msgs; - fb_api_msg_t msg; - fb_thrift_t *thft; - json_value *json; - json_value *jv; - json_value *jx; - json_value *jy; - json_value *jz; - const gchar *str; - gint64 in; - guint i; - - thft = fb_thrift_new((GByteArray*) pload, 0, TRUE); +static void +fb_api_cb_publish_typing(FbApi *api, GByteArray *pload) +{ + const gchar *str; + FbApiPrivate *priv = api->priv; + FbApiTyping typg; + FbJsonValues *values; + GError *err = NULL; + JsonNode *root; + + if (!fb_api_json_chk(api, pload->data, pload->len, &root)) { + return; + } + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.type"); + fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.sender_fbid"); + fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.state"); + fb_json_values_update(values, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); + + str = fb_json_values_next_str(values, NULL); + + if (g_ascii_strcasecmp(str, "typ") == 0) { + typg.uid = fb_json_values_next_int(values, 0); + + if (typg.uid != priv->uid) { + typg.state = fb_json_values_next_int(values, 0); + g_signal_emit_by_name(api, "typing", &typg); + } + } + + g_object_unref(values); + json_node_free(root); +} + +static gchar * +fb_api_xma_parse(FbApi *api, const gchar *body, JsonNode *root, GError **error) +{ + const gchar *str; + const gchar *url; + FbHttpValues *prms; + FbJsonValues *values; + gchar *text; + GError *err = NULL; + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, + "$.story_attachment.target.__type__.name"); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, + "$.story_attachment.url"); + fb_json_values_update(values, &err); + + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + g_object_unref(values); + return FALSE; + } + + str = fb_json_values_next_str(values, NULL); + url = fb_json_values_next_str(values, NULL); + + if (g_strcmp0(str, "ExternalUrl") == 0) { + prms = fb_http_values_new(); + fb_http_values_parse(prms, url, TRUE); + text = fb_http_values_dup_str(prms, "u", NULL); + fb_http_values_free(prms); + } else { + text = g_strdup(url); + } + + if (fb_http_urlcmp(body, text, FALSE)) { + g_free(text); + g_object_unref(values); + return NULL; + } + + g_object_unref(values); + return text; +} + +static GSList * +fb_api_message_parse_attach(FbApi *api, const gchar *mid, FbApiMessage *msg, + GSList *msgs, const gchar *body, JsonNode *root, + GError **error) +{ + const gchar *str; + FbApiMessage *dmsg; + FbId id; + FbJsonValues *values; + gchar *xma; + GError *err = NULL; + JsonNode *node; + JsonNode *xode; + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.xmaGraphQL"); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, "$.fbid"); + fb_json_values_set_array(values, FALSE, "$.deltaNewMessage.attachments"); + + while (fb_json_values_update(values, &err)) { + str = fb_json_values_next_str(values, NULL); + + if (str == NULL) { + id = fb_json_values_next_int(values, 0); + dmsg = fb_api_message_dup(msg, FALSE); + fb_api_attach(api, id, mid, dmsg); + continue; + } + + node = fb_json_node_new(str, -1, &err); + + if (G_UNLIKELY(err != NULL)) { + break; + } + + xode = fb_json_node_get_nth(node, 0); + xma = fb_api_xma_parse(api, body, xode, &err); + + if (xma != NULL) { + dmsg = fb_api_message_dup(msg, FALSE); + dmsg->text = xma; + msgs = g_slist_prepend(msgs, dmsg); + } + + json_node_free(node); + + if (G_UNLIKELY(err != NULL)) { + break; + } + } + + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + } + + g_object_unref(values); + return msgs; +} + +static void +fb_api_cb_publish_ms(FbApi *api, GByteArray *pload) +{ + const gchar *body; + const gchar *data; + const gchar *str; + FbApiMessage *dmsg; + FbApiMessage msg; + FbApiPrivate *priv = api->priv; + FbId id; + FbId oid; + FbId uid; + FbJsonValues *values; + FbThrift *thft; + gchar *stoken; + GError *err = NULL; + GSList *msgs = NULL; + guint size; + JsonNode *root; + JsonNode *node; + + thft = fb_thrift_new(pload, 0); fb_thrift_read_str(thft, NULL); - i = thft->pos; - fb_thrift_free(thft); + size = fb_thrift_get_pos(thft); + g_object_unref(thft); - g_return_if_fail(i < pload->len); + g_return_if_fail(size < pload->len); + data = (gchar *) pload->data + size; + size = pload->len - size; - if (!fb_api_json_new(api, (gchar*) pload->data + i, pload->len - i, &json)) + if (!fb_api_json_chk(api, data, size, &root)) { return; + } - msgs = NULL; + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, "$.lastIssuedSeqId"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.syncToken"); + fb_json_values_update(values, &err); - if (fb_json_str_chk(json, "syncToken", &str)) { - g_free(api->stoken); - api->stoken = g_strdup(str); - FB_API_FUNC(api, connect); - goto finish; + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); + + priv->sid = fb_json_values_next_int(values, 0); + stoken = fb_json_values_next_str_dup(values, NULL); + g_object_unref(values); + + if (G_UNLIKELY(stoken != NULL)) { + g_free(priv->stoken); + priv->stoken = stoken; + g_signal_emit_by_name(api, "connect"); + json_node_free(root); + return; } - if (!fb_json_val_chk(json, "deltas", json_array, &jv)) - goto finish; + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.deltaNewMessage.messageMetadata.offlineThreadingId"); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.deltaNewMessage.messageMetadata.actorFbId"); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.deltaNewMessage.messageMetadata" + ".threadKey.otherUserFbId"); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.deltaNewMessage.messageMetadata" + ".threadKey.threadFbId"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.deltaNewMessage.body"); + fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, + "$.deltaNewMessage.stickerId"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.deltaNewMessage.messageMetadata.messageId"); + fb_json_values_set_array(values, TRUE, "$.deltas"); + + while (fb_json_values_update(values, &err)) { + id = fb_json_values_next_int(values, 0); + + if (g_hash_table_remove(priv->mids, &id)) { + continue; + } + + fb_api_message_reset(&msg, FALSE); + uid = fb_json_values_next_int(values, 0); + oid = fb_json_values_next_int(values, 0); + msg.tid = fb_json_values_next_int(values, 0); - for (i = 0; i < jv->u.array.length; i++) { - jx = jv->u.array.values[i]; + if (uid == priv->uid) { + msg.flags |= FB_API_MESSAGE_FLAG_SELF; + msg.uid = oid; + } else { + msg.uid = uid; + } - if (!fb_json_val_chk(jx, "deltaNewMessage", json_object, &jy) || - !fb_json_val_chk(jy, "messageMetadata", json_object, &jz) || - !fb_json_int_chk(jz, "actorFbId", &in) || - (in == api->uid)) - { + if (msg.uid == 0) { continue; } - msg.uid = in; - msg.tid = 0; + body = fb_json_values_next_str(values, NULL); - if (fb_json_val_chk(jz, "threadKey", json_object, &jz) && - fb_json_int_chk(jz, "threadFbId", &in)) - { - msg.tid = in; + if (body != NULL) { + dmsg = fb_api_message_dup(&msg, FALSE); + dmsg->text = g_strdup(body); + msgs = g_slist_prepend(msgs, dmsg); } - if (fb_json_str_chk(jy, "body", &str)) { - msg.text = str; - msgs = g_slist_prepend(msgs, g_memdup(&msg, sizeof msg)); + id = fb_json_values_next_int(values, 0); + + if (id != 0) { + dmsg = fb_api_message_dup(&msg, FALSE); + fb_api_sticker(api, id, dmsg); } - if (fb_json_val_chk(jy, "attachments", json_array, &jy) && - (jy->u.array.length > 0)) - { - msg.text = "* Non-Displayable Attachments *"; - msgs = g_slist_prepend(msgs, g_memdup(&msg, sizeof msg)); + str = fb_json_values_next_str(values, NULL); + + if (str == NULL) { + continue; + } + + node = fb_json_values_get_root(values); + msgs = fb_api_message_parse_attach(api, str, &msg, msgs, body, node, + &err); + + if (G_UNLIKELY(err != NULL)) { + break; } } - msgs = g_slist_reverse(msgs); - FB_API_FUNC(api, message, msgs); + if (G_LIKELY(err == NULL)) { + msgs = g_slist_reverse(msgs); + g_signal_emit_by_name(api, "messages", msgs); + } else { + fb_api_error_emit(api, err); + } -finish: - g_slist_free_full(msgs, g_free); - json_value_free(json); + g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free); + g_object_unref(values); + json_node_free(root); } -/** - * Handles a presence states which are to be published to the user. - * - * @param api The #fb_api. - * @param pload The message payload. - **/ -static void fb_api_cb_publish_p(fb_api_t *api, const GByteArray *pload) -{ - fb_thrift_t *thft; - fb_thrift_type_t type; - fb_api_pres_t pres; - GSList *press; - gint64 i64; - gint32 i32; - guint size; - guint i; +static void +fb_api_cb_publish_p(FbApi *api, GByteArray *pload) +{ + FbApiPresence *pres; + FbThrift *thft; + FbThriftType type; + gint32 i32; + gint64 i64; + GSList *press; + guint i; + guint size; /* Start at 1 to skip the NULL byte */ - thft = fb_thrift_new((GByteArray*) pload, 1, TRUE); + thft = fb_thrift_new(pload, 1); press = NULL; /* Skip the full list boolean field */ @@ -563,28 +1455,34 @@ static void fb_api_cb_publish_p(fb_api_t *api, const GByteArray *pload) g_warn_if_fail(type == FB_THRIFT_TYPE_I32); fb_thrift_read_i32(thft, &i32); - pres.uid = i64; - pres.active = i32 != 0; - press = g_slist_prepend(press, g_memdup(&pres, sizeof pres)); - FB_UTIL_DEBUGLN("Presence: %" FB_ID_FORMAT " (%d)", i64, i32 != 0); + pres = fb_api_presence_dup(NULL); + pres->uid = i64; + pres->active = i32 != 0; + press = g_slist_prepend(press, pres); + + fb_util_debug_info("Presence: %" FB_ID_FORMAT " (%d)", + i64, i32 != 0); /* Skip the last active timestamp field */ - if (!fb_thrift_read_field(thft, &type, NULL)) + if (!fb_thrift_read_field(thft, &type, NULL)) { continue; + } g_warn_if_fail(type == FB_THRIFT_TYPE_I64); fb_thrift_read_i64(thft, NULL); /* Skip the active client bits field */ - if (!fb_thrift_read_field(thft, &type, NULL)) + if (!fb_thrift_read_field(thft, &type, NULL)) { continue; + } g_warn_if_fail(type == FB_THRIFT_TYPE_I16); fb_thrift_read_i16(thft, NULL); /* Skip the VoIP compatibility bits field */ - if (!fb_thrift_read_field(thft, &type, NULL)) + if (!fb_thrift_read_field(thft, &type, NULL)) { continue; + } g_warn_if_fail(type == FB_THRIFT_TYPE_I64); fb_thrift_read_i64(thft, NULL); @@ -595,27 +1493,32 @@ static void fb_api_cb_publish_p(fb_api_t *api, const GByteArray *pload) /* Read the field stop */ fb_thrift_read_stop(thft); - fb_thrift_free(thft); + g_object_unref(thft); press = g_slist_reverse(press); - FB_API_FUNC(api, presence, press); - g_slist_free_full(press, g_free); + g_signal_emit_by_name(api, "presences", press); + g_slist_free_full(press, (GDestroyNotify) fb_api_presence_free); } -/** - * Implements #fb_mqtt_funcs->publish((). - * - * @param mqtt The #fb_mqtt. - * @param topic The message topic. - * @param pload The message payload. - * @param data The user-defined data, which is #fb_api. - **/ -static void fb_api_cb_mqtt_publish(fb_mqtt_t *mqtt, const gchar *topic, - const GByteArray *pload, gpointer data) -{ - fb_api_t *api = data; +static void +fb_api_cb_mqtt_publish(FbMqtt *mqtt, const gchar *topic, GByteArray *pload, + gpointer data) +{ + FbApi *api = data; + gboolean comp; GByteArray *bytes; - gboolean comp; + guint i; + + static const struct { + const gchar *topic; + void (*func) (FbApi *api, GByteArray *pload); + } parsers[] = { + {"/mark_thread_response", fb_api_cb_publish_mark}, + {"/mercury", fb_api_cb_mercury}, + {"/orca_typing_notifications", fb_api_cb_publish_typing}, + {"/t_ms", fb_api_cb_publish_ms}, + {"/t_p", fb_api_cb_publish_p} + }; comp = fb_util_zcompressed(pload); @@ -630,811 +1533,1449 @@ static void fb_api_cb_mqtt_publish(fb_mqtt_t *mqtt, const gchar *topic, bytes = (GByteArray*) pload; } - fb_util_hexdump(bytes, 2, "Reading message (topic: %s):", topic); + fb_util_debug_hexdump(FB_UTIL_DEBUG_LEVEL_INFO, bytes, + "Reading message (topic: %s)", + topic); - if (g_ascii_strcasecmp(topic, "/orca_typing_notifications") == 0) - fb_api_cb_publish_tn(api, bytes); - else if (g_ascii_strcasecmp(topic, "/t_ms") == 0) - fb_api_cb_publish_ms(api, bytes); - else if (g_ascii_strcasecmp(topic, "/t_p") == 0) - fb_api_cb_publish_p(api, bytes); + for (i = 0; i < G_N_ELEMENTS(parsers); i++) { + if (g_ascii_strcasecmp(topic, parsers[i].topic) == 0) { + parsers[i].func(api, bytes); + break; + } + } - if (G_LIKELY(comp)) + if (G_LIKELY(comp)) { g_byte_array_free(bytes, TRUE); + } } -/** - * Creates a new #fb_api. The returned #fb_api should be freed with - * #fb_api_free() when no longer needed. - * - * @param funcs The #fb_api_funcs. - * @param data The user-defined data or NULL. - * @param cid The client identifier or NULL. - * @param mid The MQTT identifier or NULL. - * @param cuid The client unique identifier or NULL. - * - * @return The #fb_api or NULL on error. - **/ -fb_api_t *fb_api_new(const fb_api_funcs_t *funcs, gpointer data) +FbApi * +fb_api_new(void) { - fb_api_t *api; - - static const fb_mqtt_funcs_t muncs = { - .error = fb_api_cb_mqtt_error, - .open = fb_api_cb_mqtt_open, - .connack = fb_api_cb_mqtt_connack, - .publish = fb_api_cb_mqtt_publish - }; - - g_return_val_if_fail(funcs != NULL, NULL); - - api = g_new0(fb_api_t, 1); - memcpy(&api->funcs, funcs, sizeof *funcs); - api->data = data; - api->http = fb_http_new(FB_API_AGENT); - api->mqtt = fb_mqtt_new(&muncs, api); + FbApi *api; + FbApiPrivate *priv; + + api = g_object_new(FB_TYPE_API, NULL); + priv = api->priv; + + g_signal_connect(priv->mqtt, + "connect", + G_CALLBACK(fb_api_cb_mqtt_connect), + api); + g_signal_connect(priv->mqtt, + "error", + G_CALLBACK(fb_api_cb_mqtt_error), + api); + g_signal_connect(priv->mqtt, + "open", + G_CALLBACK(fb_api_cb_mqtt_open), + api); + g_signal_connect(priv->mqtt, + "publish", + G_CALLBACK(fb_api_cb_mqtt_publish), + api); return api; } -/** - * Rehashes the internal settings of a #fb_api. - * - * @param api The #fb_api. - **/ -void fb_api_rehash(fb_api_t *api) +void +fb_api_rehash(FbApi *api) { - sha1_state_t sha; - guint8 rb[50]; + FbApiPrivate *priv; + + g_return_if_fail(FB_IS_API(api)); + priv = api->priv; - if (api->cid == NULL) { - random_bytes(rb, sizeof rb); - api->cid = g_compute_checksum_for_data(G_CHECKSUM_MD5, rb, sizeof rb); + if (priv->cid == NULL) { + priv->cid = fb_util_randstr(32); } - if (api->mid == 0) - api->mid = g_strdup_printf("%" G_GUINT32_FORMAT, g_random_int()); + if (priv->did == NULL) { + priv->did = fb_util_uuid(); + } - if (api->cuid == NULL) { - sha1_init(&sha); - random_bytes(rb, sizeof rb); - sha1_append(&sha, rb, sizeof rb); - api->cuid = sha1_random_uuid(&sha); + if (priv->mid == 0) { + priv->mid = g_random_int(); } - if (strlen(api->cid) > 20) { - api->cid = g_realloc_n(api->cid , 21, sizeof *api->cid); - api->cid[20] = 0; + if (strlen(priv->cid) > 20) { + priv->cid = g_realloc_n(priv->cid , 21, sizeof *priv->cid); + priv->cid[20] = 0; } } -/** - * Frees all memory used by a #fb_api. - * - * @param api The #fb_api. - **/ -void fb_api_free(fb_api_t *api) +gboolean +fb_api_is_invisible(FbApi *api) { - if (G_UNLIKELY(api == NULL)) - return; + FbApiPrivate *priv; - if (api->err != NULL) - g_error_free(api->err); + g_return_val_if_fail(FB_IS_API(api), FALSE); + priv = api->priv; - fb_mqtt_free(api->mqtt); - fb_http_free(api->http); - - g_free(api->cuid); - g_free(api->mid); - g_free(api->cid); - g_free(api->stoken); - g_free(api->token); - g_free(api); + return priv->invisible; } -/** - * Handles an error within an #fb_api. This sets #fb_api->err and calls - * the error function. If the fmt argument is NULL, then #fb_api->err - * is handled. - * - * @param api The #fb_api. - * @param err The #fb_api_error. - * @param fmt The format string or NULL. - * @param ... The arguments of the format string. - **/ -void fb_api_error(fb_api_t *api, fb_api_error_t err, const gchar *fmt, ...) +void +fb_api_error(FbApi *api, FbApiError error, const gchar *format, ...) { - gchar *str; - va_list ap; + GError *err; + va_list ap; - g_return_if_fail(api != NULL); + g_return_if_fail(FB_IS_API(api)); - if (fmt != NULL) { - va_start(ap, fmt); - str = g_strdup_vprintf(fmt, ap); - va_end(ap); + va_start(ap, format); + err = g_error_new_valist(FB_API_ERROR, error, format, ap); + va_end(ap); - g_clear_error(&api->err); - g_set_error_literal(&api->err, FB_API_ERROR, err, str); - g_free(str); - } + fb_api_error_emit(api, err); +} + +void +fb_api_error_emit(FbApi *api, GError *error) +{ + g_return_if_fail(FB_IS_API(api)); + g_return_if_fail(error != NULL); - if (api->err != NULL) - FB_API_FUNC(api, error, api->err); + g_signal_emit_by_name(api, "error", error); + g_error_free(error); } -/** - * Implemented #fb_http_func for #fb_api_auth(). - * - * @param req The #fb_http_req. - * @param data The user-defined data, which is #fb_api. - **/ -static void fb_api_cb_auth(fb_http_req_t *req, gpointer data) +static void +fb_api_cb_attach(FbHttpRequest *req, gpointer data) { - fb_api_t *api = data; - json_value *json; const gchar *str; - gint64 in; - - if (!fb_api_http_chk(api, req) || - !fb_api_json_new(api, req->body, req->body_size, &json)) - { + FbApi *api = data; + FbApiMessage *msg; + FbJsonValues *values; + gchar *name; + GError *err = NULL; + GSList *msgs = NULL; + guint i; + JsonNode *root; + + static const gchar *imgexts[] = {".jpg", ".png", ".gif"}; + + if (!fb_api_http_chk(api, req, &root)) { return; } - if (!fb_json_int_chk(json, "uid", &in) || - !fb_json_str_chk(json, "access_token", &str)) - { - fb_api_error(api, FB_API_ERROR_GENERAL, "Failed to obtain user info"); - goto finish; + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.filename"); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.redirect_uri"); + fb_json_values_update(values, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); + + msg = fb_api_data_take(api, req); + str = fb_json_values_next_str(values, NULL); + name = g_ascii_strdown(str, -1); + + for (i = 0; i < G_N_ELEMENTS(imgexts); i++) { + if (g_str_has_suffix(name, imgexts[i])) { + msg->flags |= FB_API_MESSAGE_FLAG_IMAGE; + break; + } } - g_free(api->token); - api->token = g_strdup(str); + g_free(name); + msg->text = fb_json_values_next_str_dup(values, NULL); + msgs = g_slist_prepend(msgs, msg); - api->uid = in; - FB_API_FUNC(api, auth); + g_signal_emit_by_name(api, "messages", msgs); + g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free); + g_object_unref(values); + json_node_free(root); -finish: - json_value_free(json); } -/** - * Sends a authentication request. - * - * @param api The #fb_api. - * @param user The username (email). - * @param pass The password. - **/ -void fb_api_auth(fb_api_t *api, const gchar *user, const gchar *pass) +static void +fb_api_attach(FbApi *api, FbId aid, const gchar *msgid, FbApiMessage *msg) { - fb_http_req_t *req; + FbHttpRequest *req; + FbHttpValues *prms; - g_return_if_fail(api != NULL); + prms = fb_http_values_new(); + fb_http_values_set_str(prms, "mid", msgid); + fb_http_values_set_strf(prms, "aid", "%" FB_ID_FORMAT, aid); - req = fb_api_req_new(api, FB_API_BHOST, FB_API_PATH_AUTH, - fb_api_cb_auth, - "com.facebook.auth.protocol.d", - "authenticate", - "auth.login"); + req = fb_api_http_req(api, FB_API_URL_ATTACH, "getAttachment", + "messaging.getAttachment", prms, + fb_api_cb_attach); + fb_api_data_set(api, req, msg, (GDestroyNotify) fb_api_message_free); +} - fb_http_req_params_set(req, - FB_HTTP_PAIR("email", user), - FB_HTTP_PAIR("password", pass), - NULL +static void +fb_api_cb_auth(FbHttpRequest *req, gpointer data) +{ + FbApi *api = data; + FbApiPrivate *priv = api->priv; + FbJsonValues *values; + GError *err = NULL; + JsonNode *root; + + if (!fb_api_http_chk(api, req, &root)) { + return; + } + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.access_token"); + fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.uid"); + fb_json_values_update(values, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; ); - fb_api_req_send(api, req); + g_free(priv->token); + priv->token = fb_json_values_next_str_dup(values, NULL); + priv->uid = fb_json_values_next_int(values, 0); + + g_signal_emit_by_name(api, "auth"); + g_object_unref(values); + json_node_free(root); } -/** - * Implemented #fb_http_func for #fb_api_contacts(). - * - * @param req The #fb_http_req. - * @param data The user-defined data, which is #fb_api. - **/ -static void fb_api_cb_contacts(fb_http_req_t *req, gpointer data) -{ - fb_api_t *api = data; - GSList *users; - fb_api_user_t user; - json_value *json; - json_value *jv; - json_value *jx; - json_value *jy; - json_value *jz; - const gchar *str; - const gchar *uid; - const gchar *name; - guint i; - - if (!fb_api_http_chk(api, req) || - !fb_api_json_new(api, req->body, req->body_size, &json)) - { - return; +void +fb_api_auth(FbApi *api, const gchar *user, const gchar *pass) +{ + FbHttpValues *prms; + + prms = fb_http_values_new(); + fb_http_values_set_str(prms, "email", user); + fb_http_values_set_str(prms, "password", pass); + fb_api_http_req(api, FB_API_URL_AUTH, "authenticate", "auth.login", prms, + fb_api_cb_auth); +} + +static gboolean +fb_api_contact_parse(FbApi *api, FbApiUser *user, JsonNode *root, + GError **error) +{ + const gchar *str; + FbApiPrivate *priv = api->priv; + FbHttpValues *prms; + FbJsonValues *values; + GError *err = NULL; + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.represented_profile.id"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.id"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.represented_profile.friendship_status"); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, + "$.structured_name.text"); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, + "$.hugePictureUrl.uri"); + fb_json_values_update(values, &err); + + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + g_object_unref(values); + return FALSE; + } + + str = fb_json_values_next_str(values, NULL); + + if (str == NULL) { + str = fb_json_values_next_str(values, "0"); + } else { + fb_json_values_next_str(values, NULL); } - users = NULL; + user->uid = FB_ID_FROM_STR(str); + str = fb_json_values_next_str(values, NULL); - if (!fb_json_val_chk(json, "viewer", json_object, &jv) || - !fb_json_val_chk(jv, "messenger_contacts", json_object, &jv) || - !fb_json_val_chk(jv, "nodes", json_array, &jv)) + if ((str != NULL) && + (g_strcmp0(str, "ARE_FRIENDS") != 0) && + (user->uid != priv->uid)) { - fb_api_error(api, FB_API_ERROR_GENERAL, "Failed to parse contacts"); - goto finish; + g_object_unref(values); + return FALSE; } - for (i = 0; i < jv->u.array.length; i++) { - jx = jv->u.array.values[i]; + user->name = fb_json_values_next_str_dup(values, NULL); + user->icon = fb_json_values_next_str_dup(values, NULL); + + prms = fb_http_values_new(); + fb_http_values_parse(prms, user->icon, TRUE); + user->csum = fb_http_values_dup_str(prms, "oh", &err); + fb_http_values_free(prms); - /* Scattered values lead to a gnarly conditional... */ - if (!fb_json_val_chk(jx, "represented_profile", json_object, &jy) || + g_object_unref(values); + return TRUE; +} - /* Check the contact type is "user" */ - !fb_json_val_chk(jy, "__type__", json_object, &jz) || - !fb_json_str_chk(jz, "name", &str) || - (g_ascii_strcasecmp(str, "user") != 0) || +static void +fb_api_cb_contact(FbHttpRequest *req, gpointer data) +{ + FbApi *api = data; + FbApiUser user; + GError *err = NULL; + JsonNode *node; + JsonNode *root; - /* Check the contact is a friend */ - !fb_json_str_chk(jy, "friendship_status", &str) || - (g_ascii_strcasecmp(str, "ARE_FRIENDS") != 0) || + if (!fb_api_http_chk(api, req, &root)) { + return; + } - /* Obtain the contact user identifier */ - !fb_json_str_chk(jy, "id", &uid) || + node = fb_json_node_get_nth(root, 0); - /* Obtain the name of the user */ - !fb_json_val_chk(jx, "structured_name", json_object, &jy) || - !fb_json_str_chk(jy, "text", &name)) - { - continue; - } + if (node == NULL) { + fb_api_error(api, FB_API_ERROR_GENERAL, + "Failed to obtain contact information"); + json_node_free(root); + return; + } - user.uid = FB_ID_FROM_STR(uid); + fb_api_user_reset(&user, FALSE); - if (user.uid != api->uid) { - user.name = name; - users = g_slist_prepend(users, g_memdup(&user, sizeof user)); + if (!fb_api_contact_parse(api, &user, node, &err)) { + if (G_LIKELY(err == NULL)) { + fb_api_error(api, FB_API_ERROR_GENERAL, + "Failed to parse contact information"); + } else { + fb_api_error_emit(api, err); } + } else { + g_signal_emit_by_name(api, "contact", &user); } - FB_API_FUNC(api, contacts, users); + fb_api_user_reset(&user, TRUE); + json_node_free(root); +} -finish: - g_slist_free_full(users, g_free); - json_value_free(json); +void +fb_api_contact(FbApi *api, FbId uid) +{ + JsonBuilder *bldr; + + /* Object key mapping: + * 0: contact_id + * 1: big_img_size + * 2: huge_img_size + * 3: small_img_size + * 4: low_res_cover_size + * 6: media_type + */ + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_strf(bldr, "0", "%" FB_ID_FORMAT, uid); + fb_api_http_query(api, FB_API_QUERY_CONTACT, bldr, fb_api_cb_contact); } -/** - * Sends a contacts request. - * - * @param api The #fb_api. - **/ -void fb_api_contacts(fb_api_t *api) +static void +fb_api_cb_contacts(FbHttpRequest *req, gpointer data) { - fb_http_req_t *req; + FbApi *api = data; + FbApiUser *duser; + FbApiUser user; + FbJsonValues *values; + gboolean complete; + gchar *writeid = NULL; + GError *err = NULL; + GSList *users = NULL; + guint count = 0; + JsonNode *node; + JsonNode *root; + + if (!fb_api_http_chk(api, req, &root)) { + return; + } - g_return_if_fail(api != NULL); + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.graph_api_write_id"); + fb_json_values_set_array(values, FALSE, "$.viewer.messenger_contacts" + ".nodes"); - req = fb_api_req_new(api, FB_API_GHOST, FB_API_PATH_GQL, - fb_api_cb_contacts, - "com.facebook.contacts.service.d", - "FetchContactsFullQuery", - "get"); + while (fb_json_values_update(values, &err)) { + g_free(writeid); + count++; - fb_http_req_params_set(req, - FB_HTTP_PAIR("query_id", FB_API_QRYID_CONTACTS), - FB_HTTP_PAIR("query_params", "{}"), - NULL - ); + writeid = fb_json_values_next_str_dup(values, NULL); + node = fb_json_values_get_root(values); + fb_api_user_reset(&user, FALSE); + + if (fb_api_contact_parse(api, &user, node, &err)) { + duser = fb_api_user_dup(&user, FALSE); + users = g_slist_prepend(users, duser); + } else { + fb_api_user_reset(&user, TRUE); + } + + if (G_UNLIKELY(err != NULL)) { + break; + } + } + + complete = (writeid == NULL) || (count < FB_API_CONTACTS_COUNT); + + if (G_UNLIKELY(err == NULL)) { + g_signal_emit_by_name(api, "contacts", users, complete); + + if (!complete) { + fb_api_contacts_after(api, writeid); + } + } else { + fb_api_error_emit(api, err); + } - fb_api_req_send(api, req); + g_free(writeid); + g_slist_free_full(users, (GDestroyNotify) fb_api_user_free); + g_object_unref(values); + json_node_free(root); } -/** - * Connects the #fb_api to the remote services. This is mainly for - * connecting and setting up the internal #fb_mqtt. - * - * @param The #fb_api. - **/ -void fb_api_connect(fb_api_t *api) +void +fb_api_contacts(FbApi *api) { - g_return_if_fail(api != NULL); - - fb_mqtt_open(api->mqtt, FB_MQTT_HOST, FB_MQTT_PORT); + JsonBuilder *bldr; + + /* Object key mapping: + * 0: profile_types + * 1: limit + * 2: big_img_size + * 3: huge_img_size + * 4: small_img_size + * 5: low_res_cover_size + * 6: media_type + */ + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_arr_begin(bldr, "0"); + fb_json_bldr_add_str(bldr, NULL, "user"); + fb_json_bldr_arr_end(bldr); + + fb_json_bldr_add_str(bldr, "1", G_STRINGIFY(FB_API_CONTACTS_COUNT)); + fb_api_http_query(api, FB_API_QUERY_CONTACTS, bldr, + fb_api_cb_contacts); } -/** - * Disconnects the #fb_api from the remote services. This is mainly for - * disconnecting the internal #fb_mqtt. This will close the internal - * #fb_mqtt via #fb_mqtt_close(). - * - * @param The #fb_api. - **/ -void fb_api_disconnect(fb_api_t *api) +static void +fb_api_contacts_after(FbApi *api, const gchar *writeid) { - g_return_if_fail(api != NULL); + JsonBuilder *bldr; + + /* Object key mapping: + * 0: profile_types + * 1: after + * 2: limit + * 3: big_img_size + * 4: huge_img_size + * 5: small_img_size + * 6: low_res_cover_size + * 7: media_type + */ + + if (g_str_has_prefix(writeid, "contact_")) { + writeid += 8; + } + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_arr_begin(bldr, "0"); + fb_json_bldr_add_str(bldr, NULL, "user"); + fb_json_bldr_arr_end(bldr); - fb_mqtt_disconnect(api->mqtt); + fb_json_bldr_add_str(bldr, "1", writeid); + fb_json_bldr_add_str(bldr, "2", G_STRINGIFY(FB_API_CONTACTS_COUNT)); + fb_api_http_query(api, FB_API_QUERY_CONTACTS_AFTER, bldr, + fb_api_cb_contacts); } -/** - * Sends a message to a user. - * - * @param api The #fb_api. - * @param id The #fb_id of the user. - * @param thread TRUE to send to a thread identifier, otherwise FALSE. - * @param msg The message. - **/ -void fb_api_message(fb_api_t *api, fb_id_t id, gboolean thread, - const gchar *msg) -{ - guint64 msgid; - const gchar *tpfx; - gchar *escaped; +void +fb_api_connect(FbApi *api, gboolean invisible) +{ + FbApiPrivate *priv; - g_return_if_fail(api != NULL); - g_return_if_fail(msg != NULL); + g_return_if_fail(FB_IS_API(api)); + priv = api->priv; - msgid = FB_API_MSGID(g_get_real_time() / 1000, g_random_int()); - tpfx = thread ? "tfbid_" : ""; + priv->invisible = invisible; + fb_mqtt_open(priv->mqtt, FB_MQTT_HOST, FB_MQTT_PORT); +} - escaped = fb_json_str_escape(msg); +void +fb_api_disconnect(FbApi *api) +{ + FbApiPrivate *priv; - fb_api_publish(api, "/send_message2", "{" - "\"body\":\"%s\"," - "\"to\":\"%s%" FB_ID_FORMAT "\"," - "\"sender_fbid\":\"%" FB_ID_FORMAT "\"," - "\"msgid\":%" G_GUINT64_FORMAT - "}", escaped, tpfx, id, api->uid, msgid); + g_return_if_fail(FB_IS_API(api)); + priv = api->priv; - g_free(escaped); + fb_mqtt_disconnect(priv->mqtt); } -/** - * Publishes a string based message to the MQTT service. This enables - * compression of the message via zlib. - * - * @param api The #fb_api. - * @param topic The message topic. - * @param fmt The format string. - * @param ... The format arguments. - **/ -void fb_api_publish(fb_api_t *api, const gchar *topic, const gchar *fmt, ...) +void +fb_api_message(FbApi *api, FbId id, gboolean thread, const gchar *text) { + const gchar *tpfx; + FbApiPrivate *priv; + FbId *dmid; + FbId mid; + gchar *json; + JsonBuilder *bldr; + + g_return_if_fail(FB_IS_API(api)); + g_return_if_fail(text != NULL); + priv = api->priv; + + mid = FB_API_MSGID(g_get_real_time() / 1000, g_random_int()); + tpfx = thread ? "tfbid_" : ""; + + dmid = g_memdup(&mid, sizeof mid); + g_hash_table_replace(priv->mids, dmid, dmid); + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_str(bldr, "body", text); + fb_json_bldr_add_strf(bldr, "msgid", "%" FB_ID_FORMAT, mid); + fb_json_bldr_add_strf(bldr, "sender_fbid", "%" FB_ID_FORMAT, priv->uid); + fb_json_bldr_add_strf(bldr, "to", "%s%" FB_ID_FORMAT, tpfx, id); + + json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); + fb_api_publish(api, "/send_message2", "%s", json); + g_free(json); +} + +void +fb_api_publish(FbApi *api, const gchar *topic, const gchar *format, ...) +{ + FbApiPrivate *priv; GByteArray *bytes; GByteArray *cytes; - gchar *msg; - va_list ap; + gchar *msg; + va_list ap; - g_return_if_fail(api != NULL); + g_return_if_fail(FB_IS_API(api)); g_return_if_fail(topic != NULL); - g_return_if_fail(fmt != NULL); + g_return_if_fail(format != NULL); + priv = api->priv; - va_start(ap, fmt); - msg = g_strdup_vprintf(fmt, ap); + va_start(ap, format); + msg = g_strdup_vprintf(format, ap); va_end(ap); bytes = g_byte_array_new_take((guint8*) msg, strlen(msg)); cytes = fb_util_zcompress(bytes); - fb_util_hexdump(bytes, 2, "Writing message:"); - fb_mqtt_publish(api->mqtt, topic, cytes); + fb_util_debug_hexdump(FB_UTIL_DEBUG_LEVEL_INFO, bytes, + "Writing message (topic: %s)", + topic); + fb_mqtt_publish(priv->mqtt, topic, cytes); g_byte_array_free(cytes, TRUE); g_byte_array_free(bytes, TRUE); } -/** - * Implemented #fb_http_func for #fb_api_thread_create(). - * - * @param req The #fb_http_req. - * @param data The user-defined data, which is #fb_api. - **/ -static void fb_api_cb_thread_create(fb_http_req_t *req, gpointer data) +void +fb_api_read(FbApi *api, FbId id, gboolean thread) +{ + const gchar *key; + FbApiPrivate *priv; + gchar *json; + JsonBuilder *bldr; + + g_return_if_fail(FB_IS_API(api)); + priv = api->priv; + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_bool(bldr, "state", TRUE); + fb_json_bldr_add_int(bldr, "syncSeqId", priv->sid); + fb_json_bldr_add_str(bldr, "mark", "read"); + + key = thread ? "threadFbId" : "otherUserFbId"; + fb_json_bldr_add_strf(bldr, key, "%" FB_ID_FORMAT, id); + + json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); + fb_api_publish(api, "/mark_thread", "%s", json); + g_free(json); +} + +static GSList * +fb_api_cb_unread_parse_attach(FbApi *api, const gchar *mid, FbApiMessage *msg, + GSList *msgs, JsonNode *root, GError **error) { - fb_api_t *api = data; - json_value *json; const gchar *str; - fb_id_t tid; + FbApiMessage *dmsg; + FbId id; + FbJsonValues *values; + GError *err = NULL; + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.attachment_fbid"); + fb_json_values_set_array(values, FALSE, "$.blob_attachments"); + + while (fb_json_values_update(values, &err)) { + str = fb_json_values_next_str(values, NULL); + id = FB_ID_FROM_STR(str); + dmsg = fb_api_message_dup(msg, FALSE); + fb_api_attach(api, id, mid, dmsg); + } - if (!fb_api_http_chk(api, req) || - !fb_api_json_new(api, req->body, req->body_size, &json)) - { + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + } + + g_object_unref(values); + return msgs; +} + +static void +fb_api_cb_unread_msgs(FbHttpRequest *req, gpointer data) +{ + const gchar *body; + const gchar *str; + FbApi *api = data; + FbApiMessage *dmsg; + FbApiMessage msg; + FbId id; + FbId tid; + FbJsonValues *values; + gchar *xma; + GError *err = NULL; + GSList *msgs = NULL; + JsonNode *node; + JsonNode *root; + JsonNode *xode; + + if (!fb_api_http_chk(api, req, &root)) { return; } - if (!fb_json_str_chk(json, "thread_fbid", &str)) { - fb_api_error(api, FB_API_ERROR, "Failed to create thread"); - goto finish; + node = fb_json_node_get_nth(root, 0); + + if (node == NULL) { + fb_api_error(api, FB_API_ERROR_GENERAL, + "Failed to obtain unread messages"); + json_node_free(root); + return; } + values = fb_json_values_new(node); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.thread_key.thread_fbid"); + fb_json_values_update(values, &err); + + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + return; + ); + + fb_api_message_reset(&msg, FALSE); + str = fb_json_values_next_str(values, "0"); tid = FB_ID_FROM_STR(str); - FB_API_FUNC(api, thread_create, tid); + g_object_unref(values); + + values = fb_json_values_new(node); + fb_json_values_add(values, FB_JSON_TYPE_BOOL, TRUE, "$.unread"); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, + "$.message_sender.messaging_actor.id"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.message.text"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.sticker.id"); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.message_id"); + fb_json_values_set_array(values, FALSE, "$.messages.nodes"); + + while (fb_json_values_update(values, &err)) { + if (!fb_json_values_next_bool(values, FALSE)) { + continue; + } + + str = fb_json_values_next_str(values, "0"); + body = fb_json_values_next_str(values, NULL); + + fb_api_message_reset(&msg, FALSE); + msg.uid = FB_ID_FROM_STR(str); + msg.tid = tid; + + if (body != NULL) { + dmsg = fb_api_message_dup(&msg, FALSE); + dmsg->text = g_strdup(body); + msgs = g_slist_prepend(msgs, dmsg); + } + + str = fb_json_values_next_str(values, NULL); + + if (str != NULL) { + dmsg = fb_api_message_dup(&msg, FALSE); + id = FB_ID_FROM_STR(str); + fb_api_sticker(api, id, dmsg); + } + + node = fb_json_values_get_root(values); + xode = fb_json_node_get(node, "$.extensible_attachment", NULL); + + if (xode != NULL) { + xma = fb_api_xma_parse(api, body, xode, &err); + + if (xma != NULL) { + dmsg = fb_api_message_dup(&msg, FALSE); + dmsg->text = xma; + msgs = g_slist_prepend(msgs, dmsg); + } + + json_node_free(xode); + + if (G_UNLIKELY(err != NULL)) { + break; + } + } + + str = fb_json_values_next_str(values, NULL); + + if (str == NULL) { + continue; + } + + msgs = fb_api_cb_unread_parse_attach(api, str, &msg, msgs, node, &err); -finish: - json_value_free(json); + if (G_UNLIKELY(err != NULL)) { + break; + } + } + + if (G_UNLIKELY(err == NULL)) { + msgs = g_slist_reverse(msgs); + g_signal_emit_by_name(api, "messages", msgs); + } else { + fb_api_error_emit(api, err); + } + + g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free); + g_object_unref(values); + json_node_free(root); } -/** - * Sends a thread creation request. - * - * @param api The #fb_api. - * @param uids The #GSList of #fb_id. - **/ -void fb_api_thread_create(fb_api_t *api, GSList *uids) -{ - fb_http_req_t *req; - GSList *cids; - GSList *l; - GString *to; - fb_id_t *uid; - - g_return_if_fail(api != NULL); - g_warn_if_fail((uids != NULL) && (uids->next != NULL)); - - req = fb_api_req_new(api, FB_API_GHOST, FB_API_PATH_THRDS, - fb_api_cb_thread_create, - "ccom.facebook.orca.send.service.l", - "createThread", - "POST"); - - to = g_string_new(NULL); - cids = g_slist_copy(uids); - cids = g_slist_prepend(cids, &api->uid); - - for (l = cids; l != NULL; l = l->next) { - uid = l->data; +static void +fb_api_cb_unread(FbHttpRequest *req, gpointer data) +{ + const gchar *id; + FbApi *api = data; + FbJsonValues *values; + GError *err = NULL; + gint64 count; + JsonBuilder *bldr; + JsonNode *root; + + if (!fb_api_http_chk(api, req, &root)) { + return; + } + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.unread_count"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.thread_key.other_user_id"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.thread_key.thread_fbid"); + fb_json_values_set_array(values, FALSE, "$.viewer.message_threads.nodes"); + + while (fb_json_values_update(values, &err)) { + count = fb_json_values_next_int(values, -5); + + if (count < 1) { + continue; + } + + id = fb_json_values_next_str(values, NULL); - if (to->len > 0) - g_string_append_c(to, ','); + if (id == NULL) { + id = fb_json_values_next_str(values, "0"); + } - g_string_append_printf(to, "{" - "\"type\":\"id\"," - "\"id\":\"%" FB_ID_FORMAT "\"" - "}", *uid); + /* See fb_api_thread_info() for key mapping */ + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_arr_begin(bldr, "0"); + fb_json_bldr_add_str(bldr, NULL, id); + fb_json_bldr_arr_end(bldr); + + fb_json_bldr_add_str(bldr, "10", "true"); + fb_json_bldr_add_str(bldr, "11", "true"); + fb_json_bldr_add_int(bldr, "12", count); + fb_json_bldr_add_str(bldr, "13", "false"); + fb_api_http_query(api, FB_API_QUERY_THREAD, bldr, + fb_api_cb_unread_msgs); } - g_string_prepend_c(to, '['); - g_string_append_c(to, ']'); + if (G_UNLIKELY(err != NULL)) { + fb_api_error_emit(api, err); + } - fb_http_req_params_set(req, - FB_HTTP_PAIR("to", to->str), - NULL - ); + g_object_unref(values); + json_node_free(root); +} + +void +fb_api_unread(FbApi *api) +{ + FbApiPrivate *priv; + JsonBuilder *bldr; + + g_return_if_fail(FB_IS_API(api)); + priv = api->priv; - fb_api_req_send(api, req); - g_string_free(to, TRUE); - g_slist_free(cids); + if (priv->unread < 1) { + return; + } + + /* See fb_api_thread_list() for key mapping */ + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_str(bldr, "2", "true"); + fb_json_bldr_add_int(bldr, "1", priv->unread); + fb_json_bldr_add_str(bldr, "12", "true"); + fb_json_bldr_add_str(bldr, "13", "false"); + fb_api_http_query(api, FB_API_QUERY_THREADS, bldr, + fb_api_cb_unread); } -/** - * Implemented #fb_http_func for #fb_api_thread(). - * - * @param req The #fb_http_req. - * @param data The user-defined data, which is #fb_api. - **/ -static void fb_api_cb_thread_info(fb_http_req_t *req, gpointer data) -{ - fb_api_t *api = data; - fb_api_thread_t thrd; - fb_api_user_t user; - json_value *json; - json_value *jv; - json_value *jx; - gpointer mptr; - const gchar *str; - guint i; - - if (!fb_api_http_chk(api, req) || - !fb_api_json_new(api, req->body, req->body_size, &json)) - { +static void +fb_api_cb_sticker(FbHttpRequest *req, gpointer data) +{ + FbApi *api = data; + FbApiMessage *msg; + FbJsonValues *values; + GError *err = NULL; + GSList *msgs = NULL; + JsonNode *node; + JsonNode *root; + + if (!fb_api_http_chk(api, req, &root)) { return; } - /* Scattered values lead to a gnarly conditional... */ - if (!fb_json_val_chk(json, "data", json_array, &jv) || + node = fb_json_node_get_nth(root, 0); + values = fb_json_values_new(node); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.thread_image.uri"); + fb_json_values_update(values, &err); - /* Obtain the first array element */ - (jv->u.array.length != 1) || - ((jv = jv->u.array.values[0]) == NULL) || + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); - /* Check the name */ - !fb_json_str_chk(jv, "name", &str) || - (g_ascii_strcasecmp(str, "threads") != 0) || + msg = fb_api_data_take(api, req); + msg->flags |= FB_API_MESSAGE_FLAG_IMAGE; + msg->text = fb_json_values_next_str_dup(values, NULL); + msgs = g_slist_prepend(msgs, msg); - /* Obtain result array */ - !fb_json_val_chk(jv, "fql_result_set", json_array, &jv) || - (jv->u.array.length != 1) || - ((jv = jv->u.array.values[0]) == NULL) || + g_signal_emit_by_name(api, "messages", msgs); + g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free); + g_object_unref(values); + json_node_free(root); +} - /* Obtain the thread identifier */ - !fb_json_str_chk(jv, "thread_fbid", &str)) - { - fb_api_error(api, FB_API_ERROR, "Failed to fetch thread info"); - goto finish; +static void +fb_api_sticker(FbApi *api, FbId sid, FbApiMessage *msg) +{ + JsonBuilder *bldr; + FbHttpRequest *req; + + /* Object key mapping: + * 0: sticker_ids + * 1: media_type + * 2: preview_size + * 3: scaling_factor + * 4: animated_media_type + */ + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_arr_begin(bldr, "0"); + fb_json_bldr_add_strf(bldr, NULL, "%" FB_ID_FORMAT, sid); + fb_json_bldr_arr_end(bldr); + + req = fb_api_http_query(api, FB_API_QUERY_STICKER, bldr, + fb_api_cb_sticker); + fb_api_data_set(api, req, msg, (GDestroyNotify) fb_api_message_free); +} + +static gboolean +fb_api_thread_parse(FbApi *api, FbApiThread *thrd, JsonNode *root, + GError **error) +{ + const gchar *str; + FbApiPrivate *priv = api->priv; + FbApiUser *user; + FbId uid; + FbJsonValues *values; + gboolean haself = FALSE; + GError *err = NULL; + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, + "$.thread_key.thread_fbid"); + fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.name"); + fb_json_values_update(values, &err); + + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + g_object_unref(values); + return FALSE; + } + + str = fb_json_values_next_str(values, NULL); + + if (str == NULL) { + g_object_unref(values); + return FALSE; + } + + thrd->tid = FB_ID_FROM_STR(str); + thrd->topic = fb_json_values_next_str_dup(values, NULL); + g_object_unref(values); + + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, + "$.messaging_actor.id"); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, + "$.messaging_actor.name"); + fb_json_values_set_array(values, TRUE, "$.all_participants.nodes"); + + while (fb_json_values_update(values, &err)) { + str = fb_json_values_next_str(values, "0"); + uid = FB_ID_FROM_STR(str); + + if (uid != priv->uid) { + user = fb_api_user_dup(NULL, FALSE); + user->uid = uid; + user->name = fb_json_values_next_str_dup(values, NULL); + thrd->users = g_slist_prepend(thrd->users, user); + } else { + haself = TRUE; + } + } + + if (G_UNLIKELY(err != NULL)) { + g_propagate_error(error, err); + fb_api_thread_reset(thrd, TRUE); + g_object_unref(values); + return FALSE; } - thrd.tid = FB_ID_FROM_STR(str); - thrd.topic = NULL; - thrd.users = NULL; + if ((g_slist_length(thrd->users) < 2) || !haself) { + fb_api_thread_reset(thrd, TRUE); + g_object_unref(values); + return FALSE; + } - if (fb_json_str_chk(jv, "name", &str) && (strlen(str) > 0)) - thrd.topic = str; + g_object_unref(values); + return TRUE; +} - if (fb_json_val_chk(jv, "participants", json_array, &jv)) { - for (i = 0; i < jv->u.array.length; i++) { - jx = jv->u.array.values[i]; +static void +fb_api_cb_thread(FbHttpRequest *req, gpointer data) +{ + FbApi *api = data; + FbApiThread thrd; + GError *err = NULL; + JsonNode *node; + JsonNode *root; - if (!fb_json_str_chk(jx, "user_id", &str)) - continue; + if (!fb_api_http_chk(api, req, &root)) { + return; + } - user.uid = FB_ID_FROM_STR(str); + node = fb_json_node_get_nth(root, 0); - if (fb_json_str_chk(jx, "name", &str) && (user.uid != api->uid)) { - user.name = str; - mptr = g_memdup(&user, sizeof user); - thrd.users = g_slist_prepend(thrd.users, mptr); - } + if (node == NULL) { + fb_api_error(api, FB_API_ERROR_GENERAL, + "Failed to obtain thread information"); + json_node_free(root); + return; + } + + fb_api_thread_reset(&thrd, FALSE); + + if (!fb_api_thread_parse(api, &thrd, node, &err)) { + if (G_LIKELY(err == NULL)) { + fb_api_error(api, FB_API_ERROR_GENERAL, + "Failed to parse thread information"); + } else { + fb_api_error_emit(api, err); } + } else { + g_signal_emit_by_name(api, "thread", &thrd); } - FB_API_FUNC(api, thread_info, &thrd); - g_slist_free_full(thrd.users, g_free); + fb_api_thread_reset(&thrd, TRUE); + json_node_free(root); +} -finish: - json_value_free(json); +void +fb_api_thread(FbApi *api, FbId tid) +{ + JsonBuilder *bldr; + + /* Object key mapping: + * 0: thread_ids + * 1: verification_type + * 2: hash_key + * 3: small_preview_size + * 4: large_preview_size + * 5: item_count + * 6: event_count + * 7: full_screen_height + * 8: full_screen_width + * 9: medium_preview_size + * 10: fetch_users_separately + * 11: include_message_info + * 12: msg_count + * 13: include_full_user_info + * 14: profile_pic_large_size + * 15: profile_pic_medium_size + * 16: profile_pic_small_size + */ + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_arr_begin(bldr, "0"); + fb_json_bldr_add_strf(bldr, NULL, "%" FB_ID_FORMAT, tid); + fb_json_bldr_arr_end(bldr); + + fb_json_bldr_add_str(bldr, "10", "false"); + fb_json_bldr_add_str(bldr, "11", "false"); + fb_json_bldr_add_str(bldr, "13", "false"); + fb_api_http_query(api, FB_API_QUERY_THREAD, bldr, fb_api_cb_thread); } -/** - * Sends a thread info request. - * - * @param api The #fb_api. - * @param tid The thread #fb_id. - **/ -void fb_api_thread_info(fb_api_t *api, fb_id_t tid) +static void +fb_api_cb_thread_create(FbHttpRequest *req, gpointer data) { - fb_http_req_t *req; - gchar *query; + const gchar *str; + FbApi *api = data; + FbId tid; + FbJsonValues *values; + GError *err = NULL; + JsonNode *root; - g_return_if_fail(api != NULL); + if (!fb_api_http_chk(api, req, &root)) { + return; + } - req = fb_api_req_new(api, FB_API_GHOST, FB_API_PATH_FQL, - fb_api_cb_thread_info, - "com.facebook.orca.protocol.methods.u", - "fetchThreadList", - "GET"); + values = fb_json_values_new(root); + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.thread_fbid"); + fb_json_values_update(values, &err); - query = g_strdup_printf("{" - "\"threads\":\"" - "SELECT thread_fbid, participants, name " - "FROM unified_thread " - "WHERE thread_fbid='%" FB_ID_FORMAT "' " - "LIMIT 1\"" - "}", tid); + FB_API_ERROR_EMIT(api, err, + g_object_unref(values); + json_node_free(root); + return; + ); + + str = fb_json_values_next_str(values, "0"); + tid = FB_ID_FROM_STR(str); + g_signal_emit_by_name(api, "thread-create", tid); - fb_http_req_params_set(req, FB_HTTP_PAIR("q", query), NULL); - fb_api_req_send(api, req); - g_free(query); + g_object_unref(values); + json_node_free(root); } -/** - * Sends a thread invite request. - * - * @param api The #fb_api. - * @param tid The thread #fb_id. - * @param uid The user #fb_id. - **/ -void fb_api_thread_invite(fb_api_t *api, fb_id_t tid, fb_id_t uid) -{ - fb_http_req_t *req; - gchar *stid; - gchar *to; - - g_return_if_fail(api != NULL); - - req = fb_api_req_new(api, FB_API_GHOST, FB_API_PATH_PARTS, - fb_api_cb_http_bool, - "com.facebook.orca.protocol.a", - "addMembers", - "POST"); - - stid = g_strdup_printf("t_id.%" FB_ID_FORMAT, tid); - to = g_strdup_printf("[{" - "\"type\":\"id\"," - "\"id\":\"%" FB_ID_FORMAT "\"" - "}]", uid); - - fb_http_req_params_set(req, - FB_HTTP_PAIR("id", stid), - FB_HTTP_PAIR("to", to), - NULL - ); +void +fb_api_thread_create(FbApi *api, GSList *uids) +{ + FbApiPrivate *priv; + FbHttpValues *prms; + FbId *uid; + gchar *json; + GSList *l; + JsonBuilder *bldr; + + g_return_if_fail(FB_IS_API(api)); + g_warn_if_fail(g_slist_length(uids) > 1); + priv = api->priv; + + bldr = fb_json_bldr_new(JSON_NODE_ARRAY); + fb_json_bldr_obj_begin(bldr, NULL); + fb_json_bldr_add_str(bldr, "type", "id"); + fb_json_bldr_add_strf(bldr, "id", "%" FB_ID_FORMAT, priv->uid); + fb_json_bldr_obj_end(bldr); + + for (l = uids; l != NULL; l = l->next) { + uid = l->data; + fb_json_bldr_obj_begin(bldr, NULL); + fb_json_bldr_add_str(bldr, "type", "id"); + fb_json_bldr_add_strf(bldr, "id", "%" FB_ID_FORMAT, *uid); + fb_json_bldr_obj_end(bldr); + } - fb_api_req_send(api, req); - g_free(stid); - g_free(to); + json = fb_json_bldr_close(bldr, JSON_NODE_ARRAY, NULL); + prms = fb_http_values_new(); + fb_http_values_set_str(prms, "to", json); + fb_api_http_req(api, FB_API_URL_THREADS, "createThread", "POST", prms, + fb_api_cb_thread_create); + g_free(json); } -/** - * Frees all memory used by a #fb_api_thread. - * - * @param thrd The #fb_api_thread. - **/ -static void fb_api_cb_threads_free(fb_api_thread_t *thrd) +void +fb_api_thread_invite(FbApi *api, FbId tid, FbId uid) { - g_slist_free_full(thrd->users, g_free); - g_free(thrd); + FbHttpValues *prms; + gchar *json; + JsonBuilder *bldr; + + bldr = fb_json_bldr_new(JSON_NODE_ARRAY); + fb_json_bldr_obj_begin(bldr, NULL); + fb_json_bldr_add_str(bldr, "type", "id"); + fb_json_bldr_add_strf(bldr, "id", "%" FB_ID_FORMAT, uid); + fb_json_bldr_obj_end(bldr); + json = fb_json_bldr_close(bldr, JSON_NODE_ARRAY, NULL); + + prms = fb_http_values_new(); + fb_http_values_set_str(prms, "to", json); + fb_http_values_set_strf(prms, "id", "t_id.%" FB_ID_FORMAT, tid); + fb_api_http_req(api, FB_API_URL_PARTS, "addMembers", "POST", prms, + fb_api_cb_http_bool); + g_free(json); } -/** - * Implemented #fb_http_func for #fb_api_threads(). - * - * @param req The #fb_http_req. - * @param data The user-defined data, which is #fb_api. - **/ -static void fb_api_cb_thread_list(fb_http_req_t *req, gpointer data) -{ - fb_api_t *api = data; - fb_api_thread_t thrd; - fb_api_user_t user; - GSList *thrds; - json_value *json; - json_value *jv; - json_value *jx; - json_value *jy; - gpointer mptr; - const gchar *str; - guint i; - guint j; - - if (!fb_api_http_chk(api, req) || - !fb_api_json_new(api, req->body, req->body_size, &json)) - { +void +fb_api_thread_remove(FbApi *api, FbId tid, FbId uid) +{ + FbApiPrivate *priv; + FbHttpValues *prms; + gchar *json; + JsonBuilder *bldr; + + g_return_if_fail(FB_IS_API(api)); + priv = api->priv; + + prms = fb_http_values_new(); + fb_http_values_set_strf(prms, "id", "t_id.%" FB_ID_FORMAT, tid); + + if (uid == 0) { + uid = priv->uid; + } + + if (uid != priv->uid) { + bldr = fb_json_bldr_new(JSON_NODE_ARRAY); + fb_json_bldr_add_strf(bldr, NULL, "%" FB_ID_FORMAT, uid); + json = fb_json_bldr_close(bldr, JSON_NODE_ARRAY, NULL); + fb_http_values_set_str(prms, "to", json); + g_free(json); + } + + fb_api_http_req(api, FB_API_URL_PARTS, "removeMembers", "DELETE", prms, + fb_api_cb_http_bool); +} + +void +fb_api_thread_topic(FbApi *api, FbId tid, const gchar *topic) +{ + FbHttpValues *prms; + + prms = fb_http_values_new(); + fb_http_values_set_str(prms, "name", topic); + fb_http_values_set_strf(prms, "tid", "t_id.%" FB_ID_FORMAT, tid); + fb_api_http_req(api, FB_API_URL_TOPIC, "setThreadName", + "messaging.setthreadname", prms, + fb_api_cb_http_bool); +} + +static void +fb_api_cb_threads(FbHttpRequest *req, gpointer data) +{ + FbApi *api = data; + FbApiThread *dthrd; + FbApiThread thrd; + GError *err = NULL; + GList *elms; + GList *l; + GSList *thrds = NULL; + JsonArray *arr; + JsonNode *root; + + if (!fb_api_http_chk(api, req, &root)) { return; } - thrds = NULL; + arr = fb_json_node_get_arr(root, "$.viewer.message_threads.nodes", + &err); + FB_API_ERROR_EMIT(api, err, + json_node_free(root); + return; + ); - /* Scattered values lead to a gnarly conditional... */ - if (!fb_json_val_chk(json, "data", json_array, &jv) || + elms = json_array_get_elements(arr); - /* Obtain the first array element */ - (jv->u.array.length != 1) || - ((jv = jv->u.array.values[0]) == NULL) || + for (l = elms; l != NULL; l = l->next) { + fb_api_thread_reset(&thrd, FALSE); - /* Check the name */ - !fb_json_str_chk(jv, "name", &str) || - (g_ascii_strcasecmp(str, "threads") != 0) || + if (fb_api_thread_parse(api, &thrd, l->data, &err)) { + dthrd = fb_api_thread_dup(&thrd, FALSE); + thrds = g_slist_prepend(thrds, dthrd); + } else { + fb_api_thread_reset(&thrd, TRUE); + } - /* Obtain result array */ - !fb_json_val_chk(jv, "fql_result_set", json_array, &jv)) - { - fb_api_error(api, FB_API_ERROR, "Failed to fetch thread list"); - goto finish; + if (G_UNLIKELY(err != NULL)) { + break; + } } - for (i = 0; i < jv->u.array.length; i++) { - jx = jv->u.array.values[i]; + if (G_LIKELY(err == NULL)) { + thrds = g_slist_reverse(thrds); + g_signal_emit_by_name(api, "threads", thrds); + } else { + fb_api_error_emit(api, err); + } - if (!fb_json_str_chk(jx, "thread_fbid", &str)) - continue; + g_slist_free_full(thrds, (GDestroyNotify) fb_api_thread_free); + g_list_free(elms); + json_array_unref(arr); + json_node_free(root); +} - thrd.tid = FB_ID_FROM_STR(str); - thrd.topic = NULL; - thrd.users = NULL; +void +fb_api_threads(FbApi *api) +{ + JsonBuilder *bldr; + + /* Object key mapping: + * 0: folder_tag + * 1: thread_count + * 2: include_thread_info + * 3: verification_type + * 4: hash_key + * 5: small_preview_size + * 6: large_preview_size + * 7: item_count + * 8: event_count + * 9: full_screen_height + * 10: full_screen_width + * 11: medium_preview_size + * 12: fetch_users_separately + * 13: include_message_info + * 14: msg_count + * 15: <UNKNOWN> + * 16: profile_pic_large_size + * 17: profile_pic_medium_size + * 18: profile_pic_small_size + */ + + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_str(bldr, "2", "true"); + fb_json_bldr_add_str(bldr, "12", "false"); + fb_json_bldr_add_str(bldr, "13", "false"); + fb_api_http_query(api, FB_API_QUERY_THREADS, bldr, fb_api_cb_threads); +} - if (fb_json_str_chk(jx, "name", &str) && (strlen(str) > 0)) - thrd.topic = str; +void +fb_api_typing(FbApi *api, FbId uid, gboolean state) +{ + gchar *json; + JsonBuilder *bldr; - if (!fb_json_val_chk(jx, "participants", json_array, &jx)) { - thrds = g_slist_prepend(thrds, g_memdup(&thrd, sizeof thrd)); - continue; - } + bldr = fb_json_bldr_new(JSON_NODE_OBJECT); + fb_json_bldr_add_int(bldr, "state", state != 0); + fb_json_bldr_add_strf(bldr, "to", "%" FB_ID_FORMAT, uid); - for (j = 0; j < jx->u.array.length; j++) { - jy = jx->u.array.values[j]; + json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL); + fb_api_publish(api, "/typing", "%s", json); + g_free(json); +} - if (!fb_json_str_chk(jy, "user_id", &str)) - continue; +FbApiEvent * +fb_api_event_dup(const FbApiEvent *event) +{ + if (event == NULL) { + return g_new0(FbApiEvent, 1); + } - user.uid = FB_ID_FROM_STR(str); + return g_memdup(event, sizeof *event); +} - if (fb_json_str_chk(jy, "name", &str) && (user.uid != api->uid)) { - user.name = str; - mptr = g_memdup(&user, sizeof user); - thrd.users = g_slist_prepend(thrd.users, mptr); - } - } +void +fb_api_event_reset(FbApiEvent *event) +{ + g_return_if_fail(event != NULL); + memset(event, 0, sizeof *event); +} + +void +fb_api_event_free(FbApiEvent *event) +{ + if (G_LIKELY(event != NULL)) { + g_free(event); + } +} + +FbApiMessage * +fb_api_message_dup(const FbApiMessage *msg, gboolean deep) +{ + FbApiMessage *ret; - thrds = g_slist_prepend(thrds, g_memdup(&thrd, sizeof thrd)); + if (msg == NULL) { + return g_new0(FbApiMessage, 1); } - thrds = g_slist_reverse(thrds); - FB_API_FUNC(api, thread_list, thrds); + ret = g_memdup(msg, sizeof *msg); + + if (deep) { + ret->text = g_strdup(msg->text); + } -finish: - g_slist_free_full(thrds, (GDestroyNotify) fb_api_cb_threads_free); - json_value_free(json); + return ret; } -/** - * Sends a thread list request. - * - * @param api The #fb_api. - * @param limit The thread count limit. - **/ -void fb_api_thread_list(fb_api_t *api, guint limit) -{ - fb_http_req_t *req; - gchar *query; - - g_return_if_fail(api != NULL); - - req = fb_api_req_new(api, FB_API_GHOST, FB_API_PATH_FQL, - fb_api_cb_thread_list, - "com.facebook.orca.protocol.methods.u", - "fetchThreadList", - "GET"); - - query = g_strdup_printf("{" - "\"threads\":\"" - "SELECT thread_fbid, participants, name " - "FROM unified_thread " - "WHERE folder='inbox' " - "ORDER BY timestamp DESC " - "LIMIT %u\"" - "}", limit); - - fb_http_req_params_set(req, FB_HTTP_PAIR("q", query), NULL); - fb_api_req_send(api, req); - g_free(query); -} - -/** - * Sends a thread topic request. - * - * @param api The #fb_api. - * @param tid The thread #fb_id. - * @param topic The topic message. - **/ -void fb_api_thread_topic(fb_api_t *api, fb_id_t tid, const gchar *topic) +void +fb_api_message_reset(FbApiMessage *msg, gboolean deep) { - fb_http_req_t *req; - gchar *stid; + g_return_if_fail(msg != NULL); - g_return_if_fail(api != NULL); + if (deep) { + g_free(msg->text); + } - req = fb_api_req_new(api, FB_API_HOST, FB_API_PATH_TOPIC, - fb_api_cb_http_bool, - "com.facebook.orca.protocol.a", - "setThreadName", - "messaging.setthreadname"); + memset(msg, 0, sizeof *msg); +} - stid = g_strdup_printf("t_id.%" FB_ID_FORMAT, tid); +void +fb_api_message_free(FbApiMessage *msg) +{ + if (G_LIKELY(msg != NULL)) { + g_free(msg->text); + g_free(msg); + } +} - fb_http_req_params_set(req, - FB_HTTP_PAIR("tid", stid), - FB_HTTP_PAIR("name", topic), - NULL - ); +FbApiPresence * +fb_api_presence_dup(const FbApiPresence *pres) +{ + if (pres == NULL) { + return g_new0(FbApiPresence, 1); + } - fb_api_req_send(api, req); - g_free(stid); + return g_memdup(pres, sizeof *pres); } -/** - * Sends a typing state to a user. - * - * @param api The #fb_api. - * @param uid The #fb_id. - * @param state TRUE if the user is typing, otherwise FALSE. - **/ -void fb_api_typing(fb_api_t *api, fb_id_t uid, gboolean state) -{ - g_return_if_fail(api != NULL); - - fb_api_publish(api, "/typing", "{" - "\"to\":\"%" FB_ID_FORMAT "\"," - "\"state\":%d" - "}", uid, state != 0); +void +fb_api_presence_reset(FbApiPresence *pres) +{ + g_return_if_fail(pres != NULL); + memset(pres, 0, sizeof *pres); +} + +void +fb_api_presence_free(FbApiPresence *pres) +{ + if (G_LIKELY(pres != NULL)) { + g_free(pres); + } +} + +FbApiThread * +fb_api_thread_dup(const FbApiThread *thrd, gboolean deep) +{ + FbApiThread *ret; + + if (thrd == NULL) { + return g_new0(FbApiThread, 1); + } + + ret = g_memdup(thrd, sizeof *thrd); + + if (deep) { + ret->topic = g_strdup(thrd->topic); + ret->users = g_slist_copy_deep(thrd->users, + (GCopyFunc) fb_api_user_dup, + GINT_TO_POINTER(deep)); + } + + return ret; +} + +void +fb_api_thread_reset(FbApiThread *thrd, gboolean deep) +{ + g_return_if_fail(thrd != NULL); + + if (deep) { + g_slist_free_full(thrd->users, (GDestroyNotify) fb_api_user_free); + g_free(thrd->topic); + } + + memset(thrd, 0, sizeof *thrd); +} + +void +fb_api_thread_free(FbApiThread *thrd) +{ + if (G_LIKELY(thrd != NULL)) { + g_slist_free_full(thrd->users, (GDestroyNotify) fb_api_user_free); + g_free(thrd->topic); + g_free(thrd); + } +} + +FbApiTyping * +fb_api_typing_dup(const FbApiTyping *typg) +{ + if (typg == NULL) { + return g_new0(FbApiTyping, 1); + } + + return g_memdup(typg, sizeof *typg); +} + +void +fb_api_typing_reset(FbApiTyping *typg) +{ + g_return_if_fail(typg != NULL); + memset(typg, 0, sizeof *typg); +} + +void +fb_api_typing_free(FbApiTyping *typg) +{ + if (G_LIKELY(typg != NULL)) { + g_free(typg); + } +} + +FbApiUser * +fb_api_user_dup(const FbApiUser *user, gboolean deep) +{ + FbApiUser *ret; + + if (user == NULL) { + return g_new0(FbApiUser, 1); + } + + ret = g_memdup(user, sizeof *user); + + if (deep) { + ret->name = g_strdup(user->name); + ret->icon = g_strdup(user->icon); + ret->csum = g_strdup(user->csum); + } + + return ret; +} + +void +fb_api_user_reset(FbApiUser *user, gboolean deep) +{ + g_return_if_fail(user != NULL); + + if (deep) { + g_free(user->name); + g_free(user->icon); + g_free(user->csum); + } + + memset(user, 0, sizeof *user); +} + +void +fb_api_user_free(FbApiUser *user) +{ + if (G_LIKELY(user != NULL)) { + g_free(user->name); + g_free(user->icon); + g_free(user->csum); + g_free(user); + } } diff --git a/facebook/facebook-api.h b/facebook/facebook-api.h index 4db8c41..d72abbc 100644 --- a/facebook/facebook-api.h +++ b/facebook/facebook-api.h @@ -15,310 +15,821 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -/** @file **/ +#ifndef _FACEBOOK_API_H_ +#define _FACEBOOK_API_H_ -#ifndef _FACEBOOK_API_H -#define _FACEBOOK_API_H +/** + * SECTION:api + * @section_id: facebook-api + * @short_description: <filename>facebook-api.h</filename> + * @title: Facebook API + * + * The API for interacting with the Facebook Messenger protocol. + */ -#include <bitlbee.h> +#include <glib.h> +#include <glib-object.h> #include "facebook-http.h" #include "facebook-id.h" -#include "facebook-json.h" #include "facebook-mqtt.h" -#define FB_API_HOST "api.facebook.com" -#define FB_API_BHOST "b-api.facebook.com" -#define FB_API_GHOST "graph.facebook.com" -#define FB_API_AGENT "Facebook App / " PACKAGE " / " PACKAGE_VERSION -#define FB_API_KEY "256002347743983" -#define FB_API_SECRET "374e60f8b9bb6b8cbb30f78030438895" - -#define FB_API_PATH_AUTH "/method/auth.login" -#define FB_API_PATH_FQL "/fql" -#define FB_API_PATH_GQL "/graphql" -#define FB_API_PATH_PARTS "/participants" -#define FB_API_PATH_THRDS "/me/threads" -#define FB_API_PATH_TOPIC "/method/messaging.setthreadname" - -#define FB_API_QRYID_CONTACTS "10153122424521729" - -/** - * Executes one of the #fb_api_funcs. - * - * @param a The #fb_api. - * @param f The function to execute. - * @param ... The function arguments. - **/ -#define FB_API_FUNC(m, f, ...) \ - G_STMT_START { \ - if (G_LIKELY((m)->funcs.f != NULL)) { \ - (m)->funcs.f(m, ##__VA_ARGS__, (m)->data); \ - } \ - } G_STMT_END +#define FB_TYPE_API (fb_api_get_type()) +#define FB_API(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), FB_TYPE_API, FbApi)) +#define FB_API_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), FB_TYPE_API, FbApiClass)) +#define FB_IS_API(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), FB_TYPE_API)) +#define FB_IS_API_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), FB_TYPE_API)) +#define FB_API_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), FB_TYPE_API, FbApiClass)) /** - * Creates a message identifier. + * FB_API_AHOST: * - * @param m The time in miliseconds (UTC). - * @param i The random integer. + * The HTTP host for the Facebook API. + */ +#define FB_API_AHOST "https://api.facebook.com" + +/** + * FB_API_BHOST: * - * @return The 64-bit message identifier. - **/ -#define FB_API_MSGID(m, i) ((guint64) ( \ - (((guint32) i) & 0x3FFFFF) | \ - (((guint64) m) << 22) \ - )) + * The HTTP host for the Facebook BAPI. + */ +#define FB_API_BHOST "https://b-api.facebook.com" + +/** + * FB_API_GHOST: + * + * The HTTP host for the Facebook Graph API. + */ +#define FB_API_GHOST "https://graph.facebook.com" + +/** + * FB_API_WHOST: + * + * The HTTP host for the Facebook website. + */ +#define FB_API_WHOST "https://www.facebook.com" + +/** + * FB_API_KEY: + * + * The Facebook API key. + */ +#define FB_API_KEY "256002347743983" + +/** + * FB_API_SECRET: + * + * The Facebook API secret. + */ +#define FB_API_SECRET "374e60f8b9bb6b8cbb30f78030438895" + +/** + * FB_API_URL_ATTACH: + * + * The URL for attachment URL requests. + */ +#define FB_API_URL_ATTACH FB_API_AHOST "/method/messaging.getAttachment" +//#define FB_API_URL_ATTACH FB_API_AHOST "/method/messaging.attachmentRedirect" + +/** + * FB_API_URL_AUTH: + * + * The URL for authentication requests. + */ +#define FB_API_URL_AUTH FB_API_BHOST "/method/auth.login" + +/** + * FB_API_URL_GQL: + * + * The URL for GraphQL requests. + */ +#define FB_API_URL_GQL FB_API_GHOST "/graphql" + +/** + * FB_API_URL_MESSAGES: + * + * The URL for linking message threads. + */ +#define FB_API_URL_MESSAGES FB_API_WHOST "/messages" + +/** + * FB_API_URL_PARTS: + * + * The URL for participant management requests. + */ +#define FB_API_URL_PARTS FB_API_GHOST "/participants" + +/** + * FB_API_URL_THREADS: + * + * The URL for thread management requests. + */ +#define FB_API_URL_THREADS FB_API_GHOST "/me/threads" + +/** + * FB_API_URL_TOPIC: + * + * The URL for thread topic requests. + */ +#define FB_API_URL_TOPIC FB_API_AHOST "/method/messaging.setthreadname" + +/** + * FB_API_QUERY_CONTACT: + * + * The query hash for the `FetchContactQuery`. + */ +#define FB_API_QUERY_CONTACT 10153746900701729 + +/** + * FB_API_QUERY_CONTACTS: + * + * The query hash for the `FetchContactsFullQuery`. + */ +#define FB_API_QUERY_CONTACTS 10153856456271729 + +/** + * FB_API_QUERY_CONTACTS_AFTER: + * + * The query hash for the `FetchContactsFullWithAfterQuery`. + */ +#define FB_API_QUERY_CONTACTS_AFTER 10153856456281729 + +/** + * FB_API_QUERY_STICKER: + * + * The query hash for the `FetchStickersWithPreviewsQuery`. + */ +#define FB_API_QUERY_STICKER 10152877994321729 +/** + * FB_API_QUERY_THREAD: + * + * The query hash for the `ThreadQuery`. + */ +#define FB_API_QUERY_THREAD 10153919752036729 -/** The #GError codes of #fb_api. **/ -typedef enum fb_api_error fb_api_error_t; +/** + * FB_API_QUERY_THREADS: + * + * The query hash for the `ThreadListQuery`. + */ +#define FB_API_QUERY_THREADS 10153919752026729 -/** The structure for interacting with the Facebook API. **/ -typedef struct fb_api fb_api_t; +/** + * FB_API_QUERY_XMA: + * + * The query hash for the `XMAQuery`. + */ +#define FB_API_QUERY_XMA 10153919431161729 -/** The main structure for #fb_api callback functions. **/ -typedef struct fb_api_funcs fb_api_funcs_t; +/** + * FB_API_CONTACTS_COUNT: + * + * The maximum amount of contacts to fetch in a single request. If this + * value is set too high, HTTP request will fail. This is due to the + * request data being too large. + */ +#define FB_API_CONTACTS_COUNT 500 -/** The structure for representing an #fb_api message. **/ -typedef struct fb_api_msg fb_api_msg_t; +/** + * FB_API_MSGID: + * @m: The time in milliseconds. + * @i: The random integer. + * + * Creates a 64-bit message identifier in the Facebook format. + * + * Returns: The message identifier. + */ +#define FB_API_MSGID(m, i) ((guint64) ( \ + (((guint32) i) & 0x3FFFFF) | \ + (((guint64) m) << 22) \ + )) -/** The structure for representing an #fb_api presence. **/ -typedef struct fb_api_pres fb_api_pres_t; +/** + * FB_API_ERROR_EMIT: + * @a: The #FbApi. + * @e: The #FbApiError. + * @c: The code to execute. + * + * Emits a #GError on behalf of the #FbApi. + */ +#define FB_API_ERROR_EMIT(a, e, c) \ + G_STMT_START { \ + if (G_UNLIKELY((e) != NULL)) { \ + fb_api_error_emit(a, e); \ + {c;} \ + } \ + } G_STMT_END -/** The structure for representing an #fb_api thread. **/ -typedef struct fb_api_thread fb_api_thread_t; +/** + * FB_API_ERROR: + * + * The #GQuark of the domain of API errors. + */ +#define FB_API_ERROR fb_api_error_quark() -/** The structure for representing an #fb_api user typing state. **/ -typedef struct fb_api_typing fb_api_typing_t; +typedef struct _FbApi FbApi; +typedef struct _FbApiClass FbApiClass; +typedef struct _FbApiPrivate FbApiPrivate; +typedef struct _FbApiEvent FbApiEvent; +typedef struct _FbApiMessage FbApiMessage; +typedef struct _FbApiPresence FbApiPresence; +typedef struct _FbApiThread FbApiThread; +typedef struct _FbApiTyping FbApiTyping; +typedef struct _FbApiUser FbApiUser; -/** The structure for representing an #fb_api user. **/ -typedef struct fb_api_user fb_api_user_t; +/** + * FbApiError: + * @FB_API_ERROR_GENERAL: General failure. + * @FB_API_ERROR_AUTH: Authentication failure. + * @FB_API_ERROR_QUEUE: Queue failure. + * + * The error codes for the #FB_API_ERROR domain. + */ +typedef enum +{ + FB_API_ERROR_GENERAL, + FB_API_ERROR_AUTH, + FB_API_ERROR_QUEUE +} FbApiError; + +/** + * FbApiEventType: + * @FB_API_EVENT_TYPE_THREAD_USER_ADDED: A thread user was added. + * @FB_API_EVENT_TYPE_THREAD_USER_REMOVED: A thread user was removed. + * + * The #FbApiEvent types. + */ +typedef enum +{ + FB_API_EVENT_TYPE_THREAD_USER_ADDED, + FB_API_EVENT_TYPE_THREAD_USER_REMOVED +} FbApiEventType; +/** + * FbApiMessageFlags: + * @FB_API_MESSAGE_FLAG_DONE: The text has been processed. + * @FB_API_MESSAGE_FLAG_IMAGE: The text is a URL to an image. + * @FB_API_MESSAGE_FLAG_SELF: The text is from the #FbApi user. + * + * The #FbApiMessage flags. + */ +typedef enum +{ + FB_API_MESSAGE_FLAG_DONE = 1 << 0, + FB_API_MESSAGE_FLAG_IMAGE = 1 << 1, + FB_API_MESSAGE_FLAG_SELF = 1 << 2 +} FbApiMessageFlags; /** - * The #GError codes of #fb_api. - **/ -enum fb_api_error + * FbApi: + * + * Represents a Facebook Messenger connection. + */ +struct _FbApi { - FB_API_ERROR_GENERAL /** General **/ + /*< private >*/ + GObject parent; + FbApiPrivate *priv; }; /** - * The main structure for #fb_api callback functions. - **/ -struct fb_api_funcs + * FbApiClass: + * + * The base class for all #FbApi's. + */ +struct _FbApiClass { - /** - * The error function. This is called whenever an error occurs - * within the #fb_api. - * - * @param api The #fb_api. - * @param err The #GError. - * @param data The user-defined data or NULL. - **/ - void (*error) (fb_api_t *api, GError *err, gpointer data); - - /** - * The auth function. This is called whenever authentication has - * been successfully completed. This is called as a result of - * #fb_api_auth(). - * - * @param api The #fb_api. - * @param data The user-defined data or NULL. - **/ - void (*auth) (fb_api_t *api, gpointer data); - - /** - * The connect function. This is called whenever the #fb_api has - * been successfully connected. This connects to the MQTT service. - * This is called as a result of #fb_api_connect(). - * - * @param api The #fb_api. - * @param data The user-defined data or NULL. - **/ - void (*connect) (fb_api_t *api, gpointer data); - - /** - * The contacts function. This is called whenever the #fb_api has - * retrieved a set contacts. This is called as a result of - * #fb_api_contacts(). - * - * @param api The #fb_api. - * @param users The #GSList of #fb_api_user. - * @param data The user-defined data or NULL. - **/ - void (*contacts) (fb_api_t *api, GSList *users, gpointer data); - - /** - * The message function. This is called whenever the #fb_api has - * retrieved a message. - * - * @param api The #fb_api. - * @param msgs The #GSList of #fb_api_msg. - * @param data The user-defined data or NULL. - **/ - void (*message) (fb_api_t *api, GSList *msgs, gpointer data); - - /** - * The presence function. This is called whenever the #fb_api has - * retrieved a presence update. - * - * @param api The #fb_api. - * @param press The #GSList of #fb_api_pres. - * @param data The user-defined data or NULL. - **/ - void (*presence) (fb_api_t *api, GSList *press, gpointer data); - - /** - * The thread_create function. This is called whenever the #fb_api - * has created a thread. This is called as a result of - * #fb_api_thread_create(). - * - * @param api The #fb_api. - * @param tid The thread #fb_id. - * @param data The user-defined data or NULL. - **/ - void (*thread_create) (fb_api_t *api, fb_id_t tid, gpointer data); - - /** - * The thread_info function. This is called whenever the #fb_api - * has retrieved thread information. This is called as a result of - * #fb_api_thread_info(). - * - * @param api The #fb_api. - * @param thrd The #fb_api_thread. - * @param data The user-defined data or NULL. - **/ - void (*thread_info) (fb_api_t *api, fb_api_thread_t *thrd, gpointer data); - - /** - * The thread_list function. This is called whenever the #fb_api - * has retrieved a set of threads. This is called as a result of - * #fb_api_thread_list(). - * - * @param api The #fb_api. - * @param thrds The #GSList of #fb_api_thread. - * @param data The user-defined data or NULL. - **/ - void (*thread_list) (fb_api_t *api, GSList *thrds, gpointer data); - - /** - * The typing function. This is called whenever the #fb_api has - * retrieved a typing state update. - * - * @param api The #fb_api. - * @param typg The #fb_api_typing. - * @param data The user-defined data or NULL. - **/ - void (*typing) (fb_api_t *api, fb_api_typing_t *typg, gpointer data); + /*< private >*/ + GObjectClass parent_class; }; /** - * The structure for interacting with the Facebook API. - **/ -struct fb_api + * FbApiEvent: + * @type: The #FbApiEventType. + * @uid: The user #FbId. + * @tid: The thread #FbId. + * + * Represents a Facebook update event. + */ +struct _FbApiEvent { - fb_api_funcs_t funcs; /** The #fb_api_funcs. **/ - gpointer data; /** The user-defined data or NULL. **/ - - fb_http_t *http; /** The #fb_http. **/ - fb_mqtt_t *mqtt; /** The #fb_mqtt. **/ - GError *err; /** The #GError or NULL. **/ - - fb_id_t uid; /** The The #fb_id of the user. **/ - gchar *token; /** The session token. **/ - gchar *stoken; /** The sync token. **/ - gchar *cid; /** The client identifier. **/ - gchar *mid; /** The MQTT identifier. **/ - gchar *cuid; /** The client unique identifier. **/ + FbApiEventType type; + FbId uid; + FbId tid; }; /** - * The structure for representing an #fb_api message. - **/ -struct fb_api_msg + * FbApiMessage: + * @flags: The #FbApiMessageFlags. + * @uid: The user #FbId. + * @tid: The thread #FbId. + * @text: The message text. + * + * Represents a Facebook user message. + */ +struct _FbApiMessage { - fb_id_t uid; /** The #fb_id of the user. **/ - fb_id_t tid; /** The #fb_id of the thread. **/ - const gchar *text; /** The message text. **/ + FbApiMessageFlags flags; + FbId uid; + FbId tid; + gchar *text; }; /** - * The structure for representing an #fb_api presence. - **/ -struct fb_api_pres + * FbApiPresence: + * @uid: The user #FbId. + * @active: #TRUE if the user is active, otherwise #FALSE. + * + * Represents a Facebook presence message. + */ +struct _FbApiPresence { - fb_id_t uid; /** The #fb_id of the user. **/ - gboolean active; /** TRUE if the user is active. **/ + FbId uid; + gboolean active; }; /** - * The structure for representing an #fb_api thread. - **/ -struct fb_api_thread + * FbApiThread: + * @tid: The thread #FbId. + * @topic: The topic. + * @users: The #GSList of #FbApiUser's. + * + * Represents a Facebook message thread. + */ +struct _FbApiThread { - fb_id_t tid; /** The #fb_id of the thread. **/ - const gchar *topic; /** The topic of the thread or NULL. **/ - GSList *users; /** The #GList of #fb_api_user. **/ + FbId tid; + gchar *topic; + GSList *users; }; /** - * The structure for representing an #fb_api user typing state. - **/ -struct fb_api_typing + * FbApiTyping: + * @uid: The user #FbId. + * @state: #TRUE if the user is typing, otherwise #FALSE. + * + * Represents a Facebook typing message. + */ +struct _FbApiTyping { - fb_id_t uid; /** The #fb_id of the user. **/ - gboolean state; /** TRUE if the user is typing. **/ + FbId uid; + gboolean state; }; /** - * The structure for representing an #fb_api user. - **/ -struct fb_api_user + * FbApiUser: + * @uid: The user #FbId. + * @name: The name of the user. + * @icon: The icon URL. + * @csum: The checksum of @icon. + * + * Represents a Facebook user. + */ +struct _FbApiUser { - fb_id_t uid; /** The #fb_id of the user. **/ - const gchar *name; /** The name of the user. **/ + FbId uid; + gchar *name; + gchar *icon; + gchar *csum; }; +/** + * fb_api_get_type: + * + * Returns: The #GType for an #FbApi. + */ +GType +fb_api_get_type(void); -#define FB_API_ERROR fb_api_error_quark() +/** + * fb_api_error_quark: + * + * Gets the #GQuark of the domain of API errors. + * + * Returns: The #GQuark of the domain. + */ +GQuark +fb_api_error_quark(void); + +/** + * fb_api_new: + * + * Creates a new #FbApi. The returned #FbApi should be freed with + * #g_object_unref() when no longer needed. + * + * Returns: The new #FbApi. + */ +FbApi * +fb_api_new(void); + +/** + * fb_api_rehash: + * @api: The #FbApi. + * + * Rehashes and updates internal data of the #FbApi. This should be + * called whenever properties are modified. + */ +void +fb_api_rehash(FbApi *api); + +/** + * fb_api_is_invisible: + * @api: The #FbApi. + * + * Determines if the user of the #FbApi is invisible. + * + * Returns: #TRUE if the #FbApi user is invisible, otherwise #FALSE. + */ +gboolean +fb_api_is_invisible(FbApi *api); + +/** + * fb_api_error: + * @api: The #FbApi. + * @error: The #FbApiError. + * @format: The format string literal. + * @...: The arguments for @format. + * + * Emits an #FbApiError. + */ +void +fb_api_error(FbApi *api, FbApiError error, const gchar *format, ...) + G_GNUC_PRINTF(3, 4); + +/** + * fb_api_error_emit: + * @api: The #FbApi. + * @error: The #GError. + * + * Emits a #GError on an #FbApiError. + */ +void +fb_api_error_emit(FbApi *api, GError *error); + +/** + * fb_api_auth: + * @api: The #FbApi. + * @user: The Facebook user name, email, or phone number. + * @pass: The Facebook password. + * + * Sends an authentication request to Facebook. This will obtain + * session information, which is required for all other requests. + */ +void +fb_api_auth(FbApi *api, const gchar *user, const gchar *pass); + +/** + * fb_api_contact: + * @api: The #FbApi. + * @uid: The user #FbId. + * + * Sends a contact request. This will obtain the general information of + * a single contact. + */ +void +fb_api_contact(FbApi *api, FbId uid); + +/** + * fb_api_contacts: + * @api: The #FbApi. + * + * Sends a contacts request. This will obtain a full list of detailed + * contact information about the friends of the #FbApi user. + */ +void +fb_api_contacts(FbApi *api); + +/** + * fb_api_connect: + * @api: The #FbApi. + * @invisible: #TRUE to make the user invisible, otherwise #FALSE. + * + * Initializes and establishes the underlying MQTT connection. + */ +void +fb_api_connect(FbApi *api, gboolean invisible); + +/** + * fb_api_disconnect: + * @api: The #FbApi. + * + * Closes the underlying MQTT connection. + */ +void +fb_api_disconnect(FbApi *api); + +/** + * fb_api_message: + * @api: The #FbApi. + * @id: The user or thread #FbId. + * @thread: #TRUE if @id is a thread, otherwise #FALSE. + * @text: The message text. + * + * Sends a message as the user of the #FbApi to a user or a thread. + */ +void +fb_api_message(FbApi *api, FbId id, gboolean thread, const gchar *text); + +/** + * fb_api_publish: + * @api: The #FbApi. + * @topic: The topic. + * @format: The format string literal. + * @...: The arguments for @format. + * + * Publishes an MQTT message. + */ +void +fb_api_publish(FbApi *api, const gchar *topic, const gchar *format, ...) + G_GNUC_PRINTF(3, 4); + +/** + * fb_api_read: + * @api: The #FbApi. + * @id: The user or thread #FbId. + * @thread: #TRUE if @id is a thread, otherwise #FALSE. + * + * Marks a message thread as read. + */ +void +fb_api_read(FbApi *api, FbId id, gboolean thread); + +/** + * fb_api_unread: + * @api: The #FbApi. + * + * Sends an unread message request. + */ +void +fb_api_unread(FbApi *api); + +/** + * fb_api_thread: + * @api: The #FbApi. + * @tid: The thread #FbId. + * + * Sends a thread request. This will obtain the general information of + * a single thread. + */ +void +fb_api_thread(FbApi *api, FbId tid); + +/** + * fb_api_thread_create: + * @api: The #FbApi. + * @uids: The #GSList of #FbId's. + * + * Sends a thread creation request. In order to create a thread, there + * must be at least two other users in @uids. + */ +void +fb_api_thread_create(FbApi *api, GSList *uids); + +/** + * fb_api_thread_invite: + * @api: The #FbApi. + * @tid: The thread #FbId. + * @uid: The user #FbId. + * + * Sends a thread user invitation request. + */ +void +fb_api_thread_invite(FbApi *api, FbId tid, FbId uid); + +/** + * fb_api_thread_remove: + * @api: The #FbApi. + * @tid: The thread #FbId. + * @uid: The user #FbId. + * + * Sends a thread user removal request. + */ +void +fb_api_thread_remove(FbApi *api, FbId tid, FbId uid); + +/** + * fb_api_thread_topic: + * @api: The #FbApi. + * @tid: The thread #FbId. + * @topic: The topic. + * + * Sends a thread topic change request. + */ +void +fb_api_thread_topic(FbApi *api, FbId tid, const gchar *topic); -GQuark fb_api_error_quark(void); +/** + * fb_api_threads: + * @api: The #FbApi. + * + * Sends a threads request. This will obtain a full list of detailed + * thread information about the threads of the #FbApi user. + */ +void +fb_api_threads(FbApi *api); -fb_api_t *fb_api_new(const fb_api_funcs_t *funcs, gpointer data); +/** + * fb_api_typing: + * @api: The #FbApi. + * @uid: The user #FbId. + * @state: #TRUE if the #FbApi user is typing, otherwise #FALSE. + * + * Sends a typing state message for the user of the #FbApi. + */ +void +fb_api_typing(FbApi *api, FbId uid, gboolean state); -void fb_api_rehash(fb_api_t *api); +/** + * fb_api_event_dup: + * @event: The #FbApiEvent or #NULL. + * + * Duplicates an #FbApiEvent. If @event is #NULL, a new zero filled + * #FbApiEvent is returned. The returned #FbApiEvent should be freed + * with #fb_api_event_free() when no longer needed. + * + * Returns: The new #FbApiEvent. + */ +FbApiEvent * +fb_api_event_dup(const FbApiEvent *event); -void fb_api_free(fb_api_t *api); +/** + * fb_api_event_reset: + * @event: The #FbApiEvent. + * + * Resets an #FbApiEvent. + */ +void +fb_api_event_reset(FbApiEvent *event); -void fb_api_error(fb_api_t *api, fb_api_error_t err, const gchar *fmt, ...) - G_GNUC_PRINTF(3, 4); +/** + * fb_api_event_free: + * @event: The #FbApiEvent. + * + * Frees all memory used by the #FbApiEvent. + */ +void +fb_api_event_free(FbApiEvent *event); -void fb_api_auth(fb_api_t *api, const gchar *user, const gchar *pass); +/** + * fb_api_message_dup: + * @msg: The #FbApiMessage or #NULL. + * @deep: #TRUE to duplicate allocated data, otherwise #FALSE. + * + * Duplicates an #FbApiMessage. If @msg is #NULL, a new zero filled + * #FbApiMessage is returned. The returned #FbApiMessage should be + * freed with #fb_api_message_free() when no longer needed. + * + * Returns: The new #FbApiMessage. + */ +FbApiMessage * +fb_api_message_dup(const FbApiMessage *msg, gboolean deep); -void fb_api_contacts(fb_api_t *api); +/** + * fb_api_message_reset: + * @msg: The #FbApiMessage. + * @deep: #TRUE to free allocated data, otherwise #FALSE. + * + * Resets an #FbApiMessage. + */ +void +fb_api_message_reset(FbApiMessage *msg, gboolean deep); + +/** + * fb_api_message_free: + * @msg: The #FbApiMessage. + * + * Frees all memory used by the #FbApiMessage. + */ +void +fb_api_message_free(FbApiMessage *msg); + +/** + * fb_api_presence_dup: + * @pres: The #FbApiPresence or #NULL. + * + * Duplicates an #FbApiPresence. If @pres is #NULL, a new zero filled + * #FbApiPresence is returned. The returned #FbApiPresence should be + * freed with #fb_api_presence_free() when no longer needed. + * + * Returns: The new #FbApiPresence. + */ +FbApiPresence * +fb_api_presence_dup(const FbApiPresence *pres); + +/** + * fb_api_presence_reset: + * @pres: The #FbApiPresence. + * + * Resets an #FbApiPresence. + */ +void +fb_api_presence_reset(FbApiPresence *pres); -void fb_api_connect(fb_api_t *api); +/** + * fb_api_presence_free: + * @pres: The #FbApiPresence. + * + * Frees all memory used by the #FbApiPresence. + */ +void +fb_api_presence_free(FbApiPresence *pres); -void fb_api_disconnect(fb_api_t *api); +/** + * fb_api_thread_dup: + * @thrd: The #FbApiThread or #NULL. + * @deep: #TRUE to duplicate allocated data, otherwise #FALSE. + * + * Duplicates an #FbApiThread. If @thrd is #NULL, a new zero filled + * #FbApiThread is returned. The returned #FbApiThread should be freed + * with #fb_api_thread_free() when no longer needed. + * + * Returns: The new #FbApiThread. + */ +FbApiThread * +fb_api_thread_dup(const FbApiThread *thrd, gboolean deep); -void fb_api_message(fb_api_t *api, fb_id_t id, gboolean thread, - const gchar *msg); +/** + * fb_api_thread_reset: + * @thrd: The #FbApiThread. + * @deep: #TRUE to free allocated data, otherwise #FALSE. + * + * Resets an #FbApiThread. + */ +void +fb_api_thread_reset(FbApiThread *thrd, gboolean deep); -void fb_api_publish(fb_api_t *api, const gchar *topic, const gchar *fmt, ...) - G_GNUC_PRINTF(3, 4); +/** + * fb_api_thread_free: + * @thrd: The #FbApiThread. + * + * Frees all memory used by the #FbApiThread. + */ +void +fb_api_thread_free(FbApiThread *thrd); -void fb_api_thread_create(fb_api_t *api, GSList *uids); +/** + * fb_api_typing_dup: + * @typg: The #FbApiTyping or #NULL. + * + * Duplicates an #FbApiTyping. If @typg is #NULL, a new zero filled + * #FbApiTyping is returned. The returned #FbApiTyping should be freed + * with #fb_api_typing_free() when no longer needed. + * + * Returns: The new #FbApiTyping. + */ +FbApiTyping * +fb_api_typing_dup(const FbApiTyping *typg); -void fb_api_thread_info(fb_api_t *api, fb_id_t tid); +/** + * fb_api_typing_reset: + * @typg: The #FbApiTyping. + * + * Resets an #FbApiTyping. + */ +void +fb_api_typing_reset(FbApiTyping *typg); -void fb_api_thread_invite(fb_api_t *api, fb_id_t tid, fb_id_t uid); +/** + * fb_api_typing_free: + * @typg: The #FbApiTyping. + * + * Frees all memory used by the #FbApiTyping. + */ +void +fb_api_typing_free(FbApiTyping *typg); -void fb_api_thread_list(fb_api_t *api, guint limit); +/** + * fb_api_user_dup: + * @user: The #FbApiUser or #NULL. + * @deep: #TRUE to duplicate allocated data, otherwise #FALSE. + * + * Duplicates an #FbApiUser. If @user is #NULL, a new zero filled + * #FbApiUser is returned. The returned #FbApiUser should be freed with + * #fb_api_user_free() when no longer needed. + * + * Returns: The new #FbApiUser. + */ +FbApiUser * +fb_api_user_dup(const FbApiUser *user, gboolean deep); -void fb_api_thread_topic(fb_api_t *api, fb_id_t tid, const gchar *topic); +/** + * fb_api_user_reset: + * @user: The #FbApiUser. + * @deep: #TRUE to free allocated data, otherwise #FALSE. + * + * Resets an #FbApiUser. + */ +void +fb_api_user_reset(FbApiUser *user, gboolean deep); -void fb_api_typing(fb_api_t *api, fb_id_t uid, gboolean state); +/** + * fb_api_user_free: + * @user: The #FbApiUser. + * + * Frees all memory used by the #FbApiUser. + */ +void +fb_api_user_free(FbApiUser *user); -#endif /* _FACEBOOK_API_H */ +#endif /* _FACEBOOK_API_H_ */ diff --git a/facebook/facebook-data.c b/facebook/facebook-data.c new file mode 100644 index 0000000..443e00f --- /dev/null +++ b/facebook/facebook-data.c @@ -0,0 +1,404 @@ +/* + * Copyright 2014-2015 James Geboski <jgeboski@gmail.com> + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <string.h> + +#include "facebook-api.h" +#include "facebook-data.h" + +struct _FbDataPrivate +{ + FbApi *api; + struct im_connection *ic; + GQueue *msgs; + GQueue *tids; + GHashTable *evs; + GHashTable *gcs; +}; + +static const gchar *fb_props_strs[] = { + "cid", + "did", + "stoken", + "token" +}; + +G_DEFINE_TYPE(FbData, fb_data, G_TYPE_OBJECT); + +static void +fb_data_dispose(GObject *obj) +{ + FbDataPrivate *priv = FB_DATA(obj)->priv; + GHashTableIter iter; + gpointer ptr; + + g_object_unref(priv->api); + g_hash_table_iter_init(&iter, priv->evs); + + while (g_hash_table_iter_next(&iter, NULL, &ptr)) { + g_hash_table_iter_remove(&iter); + b_event_remove(GPOINTER_TO_UINT(ptr)); + } + + g_hash_table_iter_init(&iter, priv->gcs); + + while (g_hash_table_iter_next(&iter, NULL, &ptr)) { + g_hash_table_iter_remove(&iter); + imcb_chat_free(ptr); + } + + g_queue_free_full(priv->msgs, (GDestroyNotify) fb_api_message_free); + g_queue_free_full(priv->tids, g_free); + + g_hash_table_destroy(priv->evs); + g_hash_table_destroy(priv->gcs); +} + +static void +fb_data_class_init(FbDataClass *klass) +{ + GObjectClass *gklass = G_OBJECT_CLASS(klass); + + gklass->dispose = fb_data_dispose; + g_type_class_add_private(klass, sizeof (FbDataPrivate)); +} + +static void +fb_data_init(FbData *fata) +{ + FbDataPrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(fata, FB_TYPE_DATA, FbDataPrivate); + fata->priv = priv; + + priv->api = fb_api_new(); + priv->msgs = g_queue_new(); + priv->tids = g_queue_new(); + priv->evs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + priv->gcs = g_hash_table_new(g_direct_hash, g_direct_equal); +} + +FbData * +fb_data_new(account_t *acct) +{ + FbData *fata; + FbDataPrivate *priv; + + fata = g_object_new(FB_TYPE_DATA, NULL); + priv = fata->priv; + + priv->ic = imcb_new(acct); + priv->ic->proto_data = fata; + return fata; +} + +gboolean +fb_data_load(FbData *fata) +{ + account_t *acct; + const gchar *str; + FbDataPrivate *priv; + FbId id; + gboolean ret = TRUE; + guint i; + guint64 uint; + GValue val = G_VALUE_INIT; + + g_return_val_if_fail(FB_IS_DATA(fata), FALSE); + priv = fata->priv; + acct = priv->ic->acc; + + for (i = 0; i < G_N_ELEMENTS(fb_props_strs); i++) { + str = set_getstr(&acct->set, fb_props_strs[i]); + + if (str == NULL) { + ret = FALSE; + } + + g_value_init(&val, G_TYPE_STRING); + g_value_set_string(&val, str); + g_object_set_property(G_OBJECT(priv->api), fb_props_strs[i], + &val); + g_value_unset(&val); + } + + str = set_getstr(&acct->set, "mid"); + + if (str != NULL) { + uint = g_ascii_strtoull(str, NULL, 10); + g_value_init(&val, G_TYPE_UINT64); + g_value_set_uint64(&val, uint); + g_object_set_property(G_OBJECT(priv->api), "mid", &val); + g_value_unset(&val); + } else { + ret = FALSE; + } + + str = set_getstr(&acct->set, "uid"); + + if (str != NULL) { + id = FB_ID_FROM_STR(str); + g_value_init(&val, FB_TYPE_ID); + g_value_set_int64(&val, id); + g_object_set_property(G_OBJECT(priv->api), "uid", &val); + g_value_unset(&val); + } else { + ret = FALSE; + } + + fb_api_rehash(priv->api); + return ret; +} + +void +fb_data_save(FbData *fata) +{ + account_t *acct; + const gchar *str; + FbDataPrivate *priv; + gchar *dup; + guint i; + guint64 uint; + GValue val = G_VALUE_INIT; + + g_return_if_fail(FB_IS_DATA(fata)); + priv = fata->priv; + acct = priv->ic->acc; + + for (i = 0; i < G_N_ELEMENTS(fb_props_strs); i++) { + g_value_init(&val, G_TYPE_STRING); + g_object_get_property(G_OBJECT(priv->api), fb_props_strs[i], + &val); + str = g_value_get_string(&val); + set_setstr(&acct->set, fb_props_strs[i], (gchar *) str); + g_value_unset(&val); + } + + g_value_init(&val, G_TYPE_UINT64); + g_object_get_property(G_OBJECT(priv->api), "mid", &val); + uint = g_value_get_uint64(&val); + g_value_unset(&val); + + dup = g_strdup_printf("%" G_GINT64_FORMAT, uint); + set_setstr(&acct->set, "mid", dup); + g_free(dup); + + g_value_init(&val, G_TYPE_INT64); + g_object_get_property(G_OBJECT(priv->api), "uid", &val); + uint = g_value_get_int64(&val); + g_value_unset(&val); + + dup = g_strdup_printf("%" FB_ID_FORMAT, uint); + set_setstr(&acct->set, "uid", dup); + g_free(dup); +} + +void +fb_data_add_groupchat(FbData *fata, struct groupchat *gc) +{ + FbDataPrivate *priv; + + g_return_if_fail(FB_IS_DATA(fata)); + priv = fata->priv; + + g_hash_table_replace(priv->gcs, gc, gc); +} + +void +fb_data_remove_groupchat(FbData *fata, struct groupchat *gc) +{ + FbDataPrivate *priv; + + g_return_if_fail(FB_IS_DATA(fata)); + priv = fata->priv; + + g_hash_table_remove(priv->gcs, gc); +} + +void +fb_data_add_thread_head(FbData *fata, FbId tid) +{ + FbDataPrivate *priv; + FbId *dtid; + + g_return_if_fail(FB_IS_DATA(fata)); + priv = fata->priv; + + dtid = g_memdup(&tid, sizeof tid); + g_queue_push_head(priv->tids, dtid); +} + +void +fb_data_add_thread_tail(FbData *fata, FbId tid) +{ + FbDataPrivate *priv; + FbId *dtid; + + g_return_if_fail(FB_IS_DATA(fata)); + priv = fata->priv; + + dtid = g_memdup(&tid, sizeof tid); + g_queue_push_tail(priv->tids, dtid); +} + +void +fb_data_clear_threads(FbData *fata) +{ + FbDataPrivate *priv; + GList *l; + GList *n; + + g_return_if_fail(FB_IS_DATA(fata)); + priv = fata->priv; + + l = priv->tids->head; + + while (l != NULL) { + n = l->next; + g_queue_delete_link(priv->tids, l); + g_free(l->data); + l = n; + } +} + +FbId +fb_data_get_thread(FbData *fata, guint n) +{ + FbDataPrivate *priv; + FbId *tid; + + g_return_val_if_fail(FB_IS_DATA(fata), 0); + priv = fata->priv; + + tid = g_queue_peek_nth(priv->tids, n); + + if (tid == NULL) { + return 0; + } + + return *tid; +} + +void +fb_data_add_timeout(FbData *fata, const gchar *name, guint interval, + b_event_handler func, gpointer data) +{ + FbDataPrivate *priv; + gchar *key; + guint id; + + g_return_if_fail(FB_IS_DATA(fata)); + priv = fata->priv; + + fb_data_clear_timeout(fata, name, TRUE); + + key = g_strdup(name); + id = b_timeout_add(interval, func, data); + g_hash_table_replace(priv->evs, key, GUINT_TO_POINTER(id)); +} + +void +fb_data_clear_timeout(FbData *fata, const gchar *name, gboolean remove) +{ + FbDataPrivate *priv; + gpointer ptr; + guint id; + + g_return_if_fail(FB_IS_DATA(fata)); + priv = fata->priv; + + ptr = g_hash_table_lookup(priv->evs, name); + id = GPOINTER_TO_UINT(ptr); + + if ((id > 0) && remove) { + b_event_remove(id); + } + + g_hash_table_remove(priv->evs, name); +} + +FbApi * +fb_data_get_api(FbData *fata) +{ + FbDataPrivate *priv; + + g_return_val_if_fail(FB_IS_DATA(fata), NULL); + priv = fata->priv; + + return priv->api; +} + +struct im_connection * +fb_data_get_connection(FbData *fata) +{ + FbDataPrivate *priv; + + g_return_val_if_fail(FB_IS_DATA(fata), NULL); + priv = fata->priv; + + return priv->ic; +} + +void +fb_data_add_message(FbData *fata, FbApiMessage *msg) +{ + FbDataPrivate *priv; + + g_return_if_fail(FB_IS_DATA(fata)); + priv = fata->priv; + + g_queue_push_tail(priv->msgs, msg); +} + +void +fb_data_remove_message(FbData *fata, FbApiMessage *msg) +{ + FbDataPrivate *priv; + + g_return_if_fail(FB_IS_DATA(fata)); + priv = fata->priv; + + g_queue_remove(priv->msgs, msg); +} + +GSList * +fb_data_take_messages(FbData *fata, FbId uid) +{ + FbApiMessage *msg; + FbDataPrivate *priv; + GList *l; + GList *prev; + GSList *msgs = NULL; + + g_return_val_if_fail(FB_IS_DATA(fata), NULL); + priv = fata->priv; + l = priv->msgs->tail; + + while (l != NULL) { + msg = l->data; + prev = l->prev; + + if (msg->uid == uid) { + msgs = g_slist_prepend(msgs, msg); + g_queue_delete_link(priv->msgs, l); + } + + l = prev; + } + + return msgs; +} diff --git a/facebook/facebook-data.h b/facebook/facebook-data.h new file mode 100644 index 0000000..319481d --- /dev/null +++ b/facebook/facebook-data.h @@ -0,0 +1,257 @@ +/* + * Copyright 2014-2015 James Geboski <jgeboski@gmail.com> + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _FACEBOOK_DATA_H_ +#define _FACEBOOK_DATA_H_ + +/** + * SECTION:data + * @section_id: facebook-data + * @short_description: <filename>facebook-data.h</filename> + * @title: Connection Data + * + * The Connection Data. + */ + +#include <bitlbee.h> +#include <glib.h> +#include <glib-object.h> + +#include "facebook-api.h" +#include "facebook-http.h" +#include "facebook-id.h" + +#define FB_TYPE_DATA (fb_data_get_type()) +#define FB_DATA(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), FB_TYPE_DATA, FbData)) +#define FB_DATA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), FB_TYPE_DATA, FbDataClass)) +#define FB_IS_DATA(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), FB_TYPE_DATA)) +#define FB_IS_DATA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), FB_TYPE_DATA)) +#define FB_DATA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), FB_TYPE_DATA, FbDataClass)) + +typedef struct _FbData FbData; +typedef struct _FbDataClass FbDataClass; +typedef struct _FbDataPrivate FbDataPrivate; + +/** + * FbData: + * + * Represents the connection data used by #FacebookProtocol. + */ +struct _FbData +{ + /*< private >*/ + GObject parent; + FbDataPrivate *priv; +}; + +/** + * FbDataClass: + * + * The base class for all #FbData's. + */ +struct _FbDataClass +{ + /*< private >*/ + GObjectClass parent_class; +}; + +/** + * fb_data_get_type: + * + * Returns: The #GType for an #FbData. + */ +GType +fb_data_get_type(void); + +/** + * fb_data_new: + * @acct: The #account_t. + * + * Creates a new #FbData. The returned #FbData should be freed with + * #g_object_unref() when no longer needed. + * + * Returns: The new #FbData. + */ +FbData * +fb_data_new(account_t *acct); + +/** + * fb_data_load: + * @fata: The #FbData. + * + * Loads the internal data from the underlying #account_t. + * + * Return: TRUE if all of the data was loaded, otherwise FALSE. + */ +gboolean +fb_data_load(FbData *fata); + +/** + * fb_data_save: + * @fata: The #FbData. + * + * Saves the internal data to the underlying #account_t. + */ +void +fb_data_save(FbData *fata); + +/** + * fb_data_add_groupchat: + * @fata: The #FbData. + * @gc: The #groupchat. + * + * Adds a #groupchat to the the #FbData. + */ +void +fb_data_add_groupchat(FbData *fata, struct groupchat *gc); + +/** + * fb_data_remove_groupchat: + * @fata: The #FbData. + * @gc: The #groupchat. + * + * Removes a #groupchat from the the #FbData. + */ +void +fb_data_remove_groupchat(FbData *fata, struct groupchat *gc); + +/** + * fb_data_add_thread_head: + * @fata: The #FbData. + * @tid: The thread #FbId. + * + * Adds a thread identifier to the head of the list in the #FbData. + */ +void +fb_data_add_thread_head(FbData *fata, FbId tid); + +/** + * fb_data_add_thread_tail: + * @fata: The #FbData. + * @tid: The thread #FbId. + * + * Adds a thread identifier to the tail of the list in the #FbData. + */ +void +fb_data_add_thread_tail(FbData *fata, FbId tid); + +/** + * fb_data_clear_threads: + * @fata: The #FbData. + * + * Clears the thread identifier list in the #FbData. + */ +void +fb_data_clear_threads(FbData *fata); + +/** + * fb_data_get_thread: + * @fata: The #FbData. + * @index: The thread index. + * + * Gets the #FbId of a thread at @index from the #FbData. + * + * Returns: The #FbId at @index. + */ +FbId +fb_data_get_thread(FbData *fata, guint index); + +/** + * fb_data_add_timeout: + * @fata: The #FbData. + * @name: The name of the timeout. + * @interval: The time, in milliseconds, between calls to @func. + * @func: The #b_event_handler. + * @data: The data passed to @func. + * + * Adds a new callback timer. The callback is called repeatedly on the + * basis of @interval, until @func returns #FALSE. The timeout should + * be cleared with #fb_data_clear_timeout() when no longer needed. + */ +void +fb_data_add_timeout(FbData *fata, const gchar *name, guint interval, + b_event_handler func, gpointer data); + +/** + * fb_data_clear_timeout: + * @fata: The #FbData. + * @name: The name of the timeout. + * @remove: #TRUE to remove from the event loop, otherwise #FALSE. + * + * Clears and removes a callback timer. The only time @remove should be + * #FALSE, is when being called from a #GSourceFunc, which is returning + * #FALSE. + */ +void +fb_data_clear_timeout(FbData *fata, const gchar *name, gboolean remove); + +/** + * fb_data_get_api: + * @fata: The #FbData. + * + * Gets the #FbApi from the #FbData. + * + * Return: The #FbApi. + */ +FbApi * +fb_data_get_api(FbData *fata); + +/** + * fb_data_get_connection: + * @fata: The #FbData. + * + * Gets the #im_connection from the #FbData. + * + * Return: The #im_connection. + */ +struct im_connection * +fb_data_get_connection(FbData *fata); + +/** + * fb_data_add_message: + * @fata: The #FbData. + * @msg: The #FbApiMessage. + * + * Adds an #FbApiMessage to the #FbData. + */ +void +fb_data_add_message(FbData *fata, FbApiMessage *msg); + +/** + * fb_data_remove_message: + * @fata: The #FbData. + * @msg: The #FbApiMessage. + * + * Removes an #FbApiMessage from the #FbData. + */ +void +fb_data_remove_message(FbData *fata, FbApiMessage *msg); + +/** + * fb_data_take_messages: + * @fata: The #FbData. + * @uid: The user #FbId. + * + * Gets a #GSList of messages by the user #FbId from the #FbData. The + * #FbApiMessage's are removed from the #FbData. The returned #GSList + * and its #FbApiMessage's should be freed with #fb_api_message_free() + * and #g_slist_free_full() when no longer needed. + */ +GSList * +fb_data_take_messages(FbData *fata, FbId uid); + +#endif /* _FACEBOOK_DATA_H_ */ diff --git a/facebook/facebook-http.c b/facebook/facebook-http.c index e5564a8..6cd9768 100644 --- a/facebook/facebook-http.c +++ b/facebook/facebook-http.c @@ -17,179 +17,224 @@ #include <bitlbee.h> #include <string.h> +#include <url.h> #include "facebook-http.h" #include "facebook-util.h" -/** - * Gets the error domain for #fb_http. - * - * @return The #GQuark of the error domain. - **/ -GQuark fb_http_error_quark(void) +struct _FbHttpPrivate { - static GQuark q; + FbHttpValues *cookies; + GHashTable *reqs; + gchar *agent; +}; - if (G_UNLIKELY(q == 0)) - q = g_quark_from_static_string("fb-http-error-quark"); +struct _FbHttpRequestPrivate +{ + FbHttp *http; + gchar *url; + url_t purl; + gboolean post; - return q; + FbHttpValues *headers; + FbHttpValues *params; + + FbHttpFunc func; + gpointer data; + + GError *error; + struct http_request *request; + gboolean freed; +}; + +G_DEFINE_TYPE(FbHttp, fb_http, G_TYPE_OBJECT); +G_DEFINE_TYPE(FbHttpRequest, fb_http_request, G_TYPE_OBJECT); + +static void +fb_http_dispose(GObject *obj) +{ + FbHttp *http = FB_HTTP(obj); + FbHttpPrivate *priv = http->priv; + + g_free(priv->agent); + fb_http_close_requests(http); + g_hash_table_destroy(priv->reqs); + fb_http_values_free(priv->cookies); } -/** - * Creates a new #fb_http. The returned #fb_http should be freed with - * #fb_http_free() when no longer needed. - * - * @param agent The HTTP agent. - * - * @return The #fb_http or NULL on error. - **/ -fb_http_t *fb_http_new(const gchar *agent) +static void +fb_http_class_init(FbHttpClass *klass) { - fb_http_t *http; + GObjectClass *gklass = G_OBJECT_CLASS(klass); - http = g_new0(fb_http_t, 1); + gklass->dispose = fb_http_dispose; + g_type_class_add_private(klass, sizeof (FbHttpPrivate)); +} - http->agent = g_strdup(agent); - http->reqs = g_hash_table_new(g_direct_hash, g_direct_equal); - http->cookies = g_hash_table_new_full(g_str_hash, - (GEqualFunc) fb_util_str_iequal, - g_free, g_free); - return http; +static void +fb_http_init(FbHttp *http) +{ + FbHttpPrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(http, FB_TYPE_HTTP, FbHttpPrivate); + http->priv = priv; + + priv->cookies = fb_http_values_new(); + priv->reqs = g_hash_table_new(g_direct_hash, g_direct_equal); } -/** - * Frees all #fb_http_req inside a #fb_http. - * - * @param http The #fb_http. - **/ -void fb_http_free_reqs(fb_http_t *http) +static void +fb_http_req_close_nuller(struct http_request *request) { - GHashTableIter iter; - gpointer key; - if (G_UNLIKELY(http == NULL)) - return; +} - g_hash_table_iter_init(&iter, http->reqs); +static void +fb_http_request_dispose(GObject *obj) +{ + FbHttpRequestPrivate *priv = FB_HTTP_REQUEST(obj)->priv; - while (g_hash_table_iter_next(&iter, &key, NULL)) { - g_hash_table_iter_remove(&iter); - fb_http_req_free(key); + if ((priv->request != NULL) && !priv->freed) { + /* Prevent more than one call to request->func() */ + priv->request->func = fb_http_req_close_nuller; + priv->request->data = NULL; + http_close(priv->request); + } + + if (priv->error != NULL) { + g_error_free(priv->error); } + + g_free(priv->url); + fb_http_values_free(priv->headers); + fb_http_values_free(priv->params); } -/** - * Frees all memory used by a #fb_http. - * - * @param http The #fb_http. - **/ -void fb_http_free(fb_http_t *http) +static void +fb_http_request_class_init(FbHttpRequestClass *klass) { - if (G_UNLIKELY(http == NULL)) - return; + GObjectClass *gklass = G_OBJECT_CLASS(klass); - fb_http_free_reqs(http); - g_hash_table_destroy(http->reqs); - g_hash_table_destroy(http->cookies); + gklass->dispose = fb_http_request_dispose; + g_type_class_add_private(klass, sizeof (FbHttpRequestPrivate)); +} - g_free(http->agent); - g_free(http); +static void +fb_http_request_init(FbHttpRequest *req) +{ + FbHttpRequestPrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(req, FB_TYPE_HTTP_REQUEST, + FbHttpRequestPrivate); + req->priv = priv; + + priv->headers = fb_http_values_new(); + priv->params = fb_http_values_new(); } -/** - * Inserts a #va_list into a #GHashTable. - * - * @param table The #GHashTable. - * @param pair The first #fb_http_pair. - * @param ap The #va_list. - **/ -static void fb_http_tree_ins(GHashTable *table, const fb_http_pair_t *pair, - va_list ap) -{ - const fb_http_pair_t *p; - gchar *key; - gchar *val; +GQuark +fb_http_error_quark(void) +{ + static GQuark q; - for (p = pair; p != NULL; ) { - if (p->key == NULL) - continue; + if (G_UNLIKELY(q == 0)) + q = g_quark_from_static_string("fb-http-error-quark"); - key = g_strdup(p->key); - val = g_strdup(p->val); + return q; +} - g_hash_table_replace(table, key, val); - p = va_arg(ap, const fb_http_pair_t*); - } +FbHttp * +fb_http_new(const gchar *agent) +{ + FbHttp *http; + FbHttpPrivate *priv; + + http = g_object_new(FB_TYPE_HTTP, NULL); + priv = http->priv; + priv->agent = g_strdup(agent); + return http; } -/** - * Sets cookies from #fb_http_pair. If a cookie already exists, it is - * overwritten with the new value. - * - * @param http The #fb_http. - * @param pair The first #fb_http_pair. - * @param ... The additional #fb_http_pair. - **/ -void fb_http_cookies_set(fb_http_t *http, const fb_http_pair_t *pair, ...) +FbHttpValues * +fb_http_get_cookies(FbHttp *http) { - va_list ap; + FbHttpPrivate *priv; - g_return_if_fail(http != NULL); + g_return_val_if_fail(FB_IS_HTTP(http), NULL); + priv = http->priv; - va_start(ap, pair); - fb_http_tree_ins(http->cookies, pair, ap); - va_end(ap); + return priv->cookies; } -/** - * Parses cookies from a #fb_http_req. If a cookie already exists, it - * is overwritten with the new value. - * - * @param http The #fb_http. - * @param req The #fb_http_req. - **/ -void fb_http_cookies_parse_req(fb_http_t *http, const fb_http_req_t *req) +void +fb_http_close_requests(FbHttp *http) { + FbHttpPrivate *priv; + FbHttpRequest *req; + GHashTableIter iter; + + g_return_if_fail(FB_IS_HTTP(http)); + priv = http->priv; + + g_hash_table_iter_init(&iter, priv->reqs); + + while (g_hash_table_iter_next(&iter, (gpointer) &req, NULL)) { + g_hash_table_iter_remove(&iter); + g_object_unref(req); + } +} + +void +fb_http_cookies_parse_request(FbHttp *http, FbHttpRequest *req) +{ + FbHttpPrivate *hriv; + FbHttpRequestPrivate *priv; gchar **hdrs; gchar **kv; - gchar *str; - gsize i; - gsize j; + gchar *str; + guint i; + guint j; - g_return_if_fail(http != NULL); - g_return_if_fail(req != NULL); + g_return_if_fail(FB_IS_HTTP(http)); + g_return_if_fail(FB_IS_HTTP_REQUEST(req)); + hriv = http->priv; + priv = req->priv; - if (req->request == NULL) + if (priv->request == NULL) { return; + } - hdrs = g_strsplit(req->request->reply_headers, "\r\n", 0); + hdrs = g_strsplit(priv->request->reply_headers, "\r\n", 0); for (i = 0; hdrs[i] != NULL; i++) { - if (g_ascii_strncasecmp(hdrs[i], "Set-Cookie", 10) != 0) + if (g_ascii_strncasecmp(hdrs[i], "Set-Cookie", 10) != 0) { continue; + } str = strchr(hdrs[i], ';'); - if (str != NULL) + if (str != NULL) { str[0] = 0; + } str = strchr(hdrs[i], ':'); - if (str == NULL) + if (str == NULL) { continue; + } str = g_strstrip(++str); kv = g_strsplit(str, "=", 2); for (j = 0; kv[j] != NULL; j++) { - str = fb_http_uri_unescape(kv[j]); + str = g_uri_unescape_string(kv[j], NULL); g_free(kv[j]); kv[j] = str; } - if (g_strv_length(kv) > 1) - fb_http_cookies_set(http, FB_HTTP_PAIR(kv[0], kv[1]), NULL); + if (g_strv_length(kv) > 1) { + fb_http_values_set_str(hriv->cookies, kv[0], kv[1]); + } g_strfreev(kv); } @@ -197,568 +242,625 @@ void fb_http_cookies_parse_req(fb_http_t *http, const fb_http_req_t *req) g_strfreev(hdrs); } -/** - * Parses cookies from a string. If a cookie already exists, it is - * overwritten with the new value. - * - * @param http The #fb_http. - * @param data The string. - **/ -void fb_http_cookies_parse_str(fb_http_t *http, const gchar *data) +FbHttpRequest * +fb_http_request_new(FbHttp *http, const gchar *url, gboolean post, + FbHttpFunc func, gpointer data) { - gchar **ckis; - gchar **kv; - gchar *str; - gsize i; - gsize j; + FbHttpPrivate *hriv; + FbHttpRequest *req; + FbHttpRequestPrivate *priv; - g_return_if_fail(http != NULL); - g_return_if_fail(data != NULL); + g_return_val_if_fail(FB_IS_HTTP(http), NULL); + g_return_val_if_fail(url != NULL, NULL); + g_return_val_if_fail(func != NULL, NULL); - ckis = g_strsplit(data, ";", 0); + req = g_object_new(FB_TYPE_HTTP_REQUEST, NULL); + priv = req->priv; + hriv = http->priv; - for (i = 0; ckis[i] != NULL; i++) { - str = g_strstrip(ckis[i]); - kv = g_strsplit(str, "=", 2); + if (!url_set(&priv->purl, url)) { + g_object_unref(req); + return NULL; + } - for (j = 0; kv[j] != NULL; j++) { - str = fb_http_uri_unescape(kv[j]); - g_free(kv[j]); - kv[j] = str; + priv->http = http; + priv->url = g_strdup(url); + priv->post = post; + priv->func = func; + priv->data = data; + + if (hriv->agent != NULL) { + fb_http_values_set_str(priv->headers, "User-Agent", hriv->agent); + } + + fb_http_values_set_str(priv->headers, "Host", priv->purl.host); + fb_http_values_set_str(priv->headers, "Accept", "*/*"); + fb_http_values_set_str(priv->headers, "Connection", "Close"); + + return req; +} + +const gchar * +fb_http_request_get_data(FbHttpRequest *req, gsize *size) +{ + FbHttpRequestPrivate *priv; + + g_return_val_if_fail(FB_IS_HTTP_REQUEST(req), NULL); + priv = req->priv; + + if (priv->request == NULL) { + if (size != NULL) { + *size = 0; } - if (g_strv_length(kv) > 1) - fb_http_cookies_set(http, FB_HTTP_PAIR(kv[0], kv[1]), NULL); + return NULL; + } - g_strfreev(kv); + if (size != NULL) { + *size = priv->request->body_size; } - g_strfreev(ckis); + return priv->request->reply_body; } -/** - * Gets a string representation of the cookies of a #fb_http. The - * returned string should be freed with #g_free() when no longer - * needed. - * - * @param http The #fb_http. - * - * @return The string representation of the cookies. - **/ -gchar *fb_http_cookies_str(fb_http_t *http) +FbHttpValues * +fb_http_request_get_headers(FbHttpRequest *req) { - GHashTableIter iter; - GString *gstr; - gchar *key; - gchar *val; - gchar *str; + FbHttpRequestPrivate *priv; - g_return_val_if_fail(http != NULL, NULL); + g_return_val_if_fail(FB_IS_HTTP_REQUEST(req), NULL); + priv = req->priv; - gstr = g_string_sized_new(128); - g_hash_table_iter_init(&iter, http->cookies); + return priv->headers; +} - while (g_hash_table_iter_next(&iter, (gpointer*) &key, (gpointer*) &val)) { - if (val == NULL) - val = ""; +FbHttpValues * +fb_http_request_get_params(FbHttpRequest *req) +{ + FbHttpRequestPrivate *priv; - key = fb_http_uri_escape(key); - val = fb_http_uri_escape(val); + g_return_val_if_fail(FB_IS_HTTP_REQUEST(req), NULL); + priv = req->priv; - str = (gstr->len > 0) ? "; " : ""; - g_string_append_printf(gstr, "%s%s=%s", str, key, val); + return priv->params; +} - g_free(key); - g_free(val); - } +const gchar * +fb_http_request_get_status(FbHttpRequest *req, gint *code) +{ + FbHttpRequestPrivate *priv; - str = g_strdup(gstr->str); - g_string_free(gstr, TRUE); + g_return_val_if_fail(FB_IS_HTTP_REQUEST(req), NULL); + priv = req->priv; - return str; -} + if (priv->request == NULL) { + if (code != NULL) { + *code = 0; + } -/** - * Creates a new #fb_http_req. The returned #fb_http_req should be - * freed with #fb_http_req_free() when no longer needed. - * - * @param http The #fb_http. - * @param host The hostname. - * @param port The port number. - * @param path The pathname. - * @param func The user callback function or NULL. - * @param data The user define data or NULL. - * - * @return The #fb_http_req or NULL on error. - **/ -fb_http_req_t *fb_http_req_new(fb_http_t *http, const gchar *host, - gint port, const gchar *path, - fb_http_func_t func, gpointer data) -{ - fb_http_req_t *req; - - req = g_new0(fb_http_req_t, 1); - - req->http = http; - req->host = g_strdup(host); - req->port = port; - req->path = g_strdup(path); - req->func = func; - req->data = data; - - req->headers = g_hash_table_new_full(g_str_hash, - (GEqualFunc) fb_util_str_iequal, - g_free, g_free); - req->params = g_hash_table_new_full(g_str_hash, - (GEqualFunc) fb_util_str_iequal, - g_free, g_free); - - fb_http_req_headers_set(req, - FB_HTTP_PAIR("User-Agent", http->agent), - FB_HTTP_PAIR("Host", host), - FB_HTTP_PAIR("Accept", "*/*"), - FB_HTTP_PAIR("Connection", "Close"), - NULL - ); + return NULL; + } - return req; + if (code != NULL) { + *code = priv->request->status_code; + } + + return priv->request->status_string; } -/** - * Implemented #http_input_function for nulling the callback operation. - * - * @param request The #http_request. - **/ -static void fb_http_req_close_nuller(struct http_request *request) +GError * +fb_http_request_take_error(FbHttpRequest *req) { + FbHttpRequestPrivate *priv; + GError *err; + + g_return_val_if_fail(FB_IS_HTTP_REQUEST(req), NULL); + priv = req->priv; + + err = priv->error; + priv->error = NULL; + return err; } -/** - * Closes the underlying #http_request. - * - * @param callback TRUE to execute the callback, otherwise FALSE. - * - * @param req The #fb_http_req. - **/ -static void fb_http_req_close(fb_http_req_t *req, gboolean callback) +static void +fb_http_request_debug(FbHttpRequest *req, gboolean response, + const gchar *header, const gchar *body) { - g_return_if_fail(req != NULL); + const gchar *action; + const gchar *method; + const gchar *status; + FbHttpRequestPrivate *priv = req->priv; + gchar **lines; + gchar *str; + gint code; + guint i; - b_event_remove(req->toid); + status = fb_http_request_get_status(req, &code); + action = response ? "Response" : "Request"; + method = priv->post ? "POST" : "GET"; - if ((req->err == NULL) && (req->scode == 0)) { - g_set_error(&req->err, FB_HTTP_ERROR, FB_HTTP_ERROR_CLOSED, - "Request closed"); + if (status != NULL) { + str = g_strdup_printf(" (%s)", status); + } else if (response) { + str = g_strdup_printf(" (%d)", code); + } else { + str = g_strdup(""); } - if (callback && (req->func != NULL)) - req->func(req, req->data); + fb_util_debug_info("%s %s (%p): %s%s", + method, action, req, + priv->url, str); + g_free(str); - if (req->request != NULL) { - /* Prevent more than one call to request->func() */ - req->request->func = fb_http_req_close_nuller; - req->request->data = NULL; + if ((header != NULL) && (strlen(header) > 0)) { + lines = g_strsplit(header, "\n", 0); - if (!(req->request->flags & FB_HTTP_CLIENT_FREED)) - http_close(req->request); + for (i = 0; lines[i] != NULL; i++) { + fb_util_debug_info(" %s", lines[i]); + } + + g_strfreev(lines); + } else { + fb_util_debug_info(" ** No header data **"); + fb_util_debug_info("%s", ""); } - req->status = NULL; - req->scode = 0; - req->header = NULL; - req->body = NULL; - req->body_size = 0; - req->toid = 0; - req->request = NULL; + if ((body != NULL) && (strlen(body) > 0)) { + lines = g_strsplit(body, "\n", 0); + + for (i = 0; lines[i] != NULL; i++) { + fb_util_debug_info(" %s", lines[i]); + } + + g_strfreev(lines); + } else { + fb_util_debug_info(" ** No body data **"); + } } -/** - * Frees all memory used by a #fb_http_req. - * - * @param req The #fb_http_req. - **/ -void fb_http_req_free(fb_http_req_t *req) +static void +fb_http_request_cb(struct http_request *request) { - if (G_UNLIKELY(req == NULL)) - return; + const gchar *status; + FbHttpRequest *req = request->data; + FbHttpRequestPrivate *priv = req->priv; + gint code; + + status = fb_http_request_get_status(req, &code); + g_hash_table_remove(priv->http->priv->reqs, req); + priv->freed = TRUE; + + switch (code) { + case 200: + case 301: + case 302: + case 303: + case 307: + break; - fb_http_req_close(req, TRUE); + default: + g_set_error(&priv->error, FB_HTTP_ERROR, code, "%s", status); + } - if (req->err != NULL) - g_error_free(req->err); + fb_http_request_debug(req, TRUE, priv->request->reply_headers, + priv->request->reply_body); - g_hash_table_destroy(req->headers); - g_hash_table_destroy(req->params); + if (G_LIKELY(priv->func != NULL)) { + priv->func(req, priv->data); + } - g_free(req->path); - g_free(req->host); - g_free(req); + g_object_unref(req); } -#ifdef DEBUG_FACEBOOK -static void fb_http_req_debug(fb_http_req_t *req, gboolean response, - const gchar *header, const gchar *body) +void +fb_http_request_send(FbHttpRequest *req) { - const gchar *act; - const gchar *type; - const gchar *prot; - gchar *str; - gchar **ls; - guint i; - - if (req->err != NULL) - str = g_strdup_printf(" (%s)", req->err->message); - else if (req->status != NULL) - str = g_strdup_printf(" (%s)", req->status); - else - str = g_strdup(""); + FbHttpPrivate *hriv; + FbHttpRequestPrivate *priv; + gchar *hdrs; + gchar *prms; + gchar *str; + gsize size; - act = response ? "Response" : "Request"; - type = (req->flags & FB_HTTP_REQ_FLAG_POST) ? "POST" : "GET"; - prot = (req->flags & FB_HTTP_REQ_FLAG_SSL) ? "https" : "http"; + g_return_if_fail(FB_IS_HTTP_REQUEST(req)); + priv = req->priv; + hriv = priv->http->priv; - FB_UTIL_DEBUGLN("%s %s (%p): %s://%s:%d%s%s", - type, act, req, prot, - req->host, req->port, - req->path, str); - g_free(str); + if (g_hash_table_size(hriv->cookies) > 0) { + str = fb_http_values_str_cookies(hriv->cookies); + fb_http_values_set_str(priv->headers, "Cookie", str); + g_free(str); + } - if (req->rsc > 0) - FB_UTIL_DEBUGLN("Reattempt: #%u", req->rsc); + prms = fb_http_values_str_params(priv->params, NULL); - if ((header != NULL) && (strlen(header) > 0)) { - ls = g_strsplit(header, "\n", 0); + if (priv->post) { + size = strlen(prms); + fb_http_values_set_strf(priv->headers, "Content-Length", + "%" G_GSIZE_FORMAT, size); + fb_http_values_set_str(priv->headers, "Content-Type", + "application/x-www-form-urlencoded"); + } - for (i = 0; ls[i] != NULL; i++) - FB_UTIL_DEBUGLN(" %s", ls[i]); + hdrs = fb_http_values_str_headers(priv->headers); - g_strfreev(ls); + if (priv->post) { + str = g_strdup_printf("POST %s HTTP/1.1\r\n%s\r\n%s", + priv->purl.file, hdrs, prms); } else { - FB_UTIL_DEBUGLN(" ** No header data **"); - FB_UTIL_DEBUGLN(""); + str = g_strdup_printf("GET %s?%s HTTP/1.1\r\n%s\r\n", + priv->purl.file, prms, hdrs); } - if ((body != NULL) && (strlen(body) > 0)) { - ls = g_strsplit(body, "\n", 0); + fb_http_request_debug(req, FALSE, hdrs, prms); + priv->request = http_dorequest(priv->purl.host, priv->purl.port, + priv->purl.proto == PROTO_HTTPS, + str, fb_http_request_cb, req); - for (i = 0; ls[i] != NULL; i++) - FB_UTIL_DEBUGLN(" %s", ls[i]); + g_free(hdrs); + g_free(prms); + g_free(str); - g_strfreev(ls); - } else { - FB_UTIL_DEBUGLN(" ** No body data **"); + if (G_UNLIKELY(priv->request == NULL)) { + g_set_error(&priv->error, FB_HTTP_ERROR, FB_HTTP_ERROR_INIT, + "Failed to init request"); + + if (G_LIKELY(priv->func != NULL)) { + priv->func(req, priv->data); + } + + g_object_unref(req); + return; } + + g_hash_table_replace(hriv->reqs, req, req); } -#endif /* DEBUG_FACEBOOK */ -/** - * Sets headers from #fb_http_pair. If a header already exists, it is - * overwritten with the new value. - * - * @param req The #fb_http_req. - * @param pair The first #fb_http_pair. - * @param ... The additional #fb_http_pair. - **/ -void fb_http_req_headers_set(fb_http_req_t *req, const fb_http_pair_t *pair, - ...) +gboolean +fb_http_urlcmp(const gchar *url1, const gchar *url2, gboolean protocol) { - va_list ap; + gboolean ret; + url_t purl1; + url_t purl2; - g_return_if_fail(req != NULL); + if ((url1 == NULL) || (url2 == NULL)) { + return url1 == url2; + } - va_start(ap, pair); - fb_http_tree_ins(req->headers, pair, ap); - va_end(ap); + if (!url_set(&purl1, url1) || !url_set(&purl2, url2)) { + return g_ascii_strcasecmp(url1, url2) == 0; + } + + ret = (g_ascii_strcasecmp(purl1.host, purl2.host) == 0) && + (g_strcmp0(purl1.file, purl2.file) == 0) && + (g_strcmp0(purl1.user, purl2.user) == 0) && + (g_strcmp0(purl1.pass, purl2.pass) == 0); + + if (ret && protocol) { + ret = (purl1.proto == purl2.proto) && (purl1.port == purl2.port); + } + + return ret; } -/** - * Sets parameters from #fb_http_pair. If a parameter already exists, - * it is overwritten with the new value. - * - * @param req The #fb_http_req. - * @param pair The first #fb_http_pair. - * @param ... The additional #fb_http_pair. - **/ -void fb_http_req_params_set(fb_http_req_t *req, const fb_http_pair_t *pair, - ...) +static gboolean +fb_http_value_equal(gconstpointer a, gconstpointer b) { - va_list ap; + return g_ascii_strcasecmp(a, b) == 0; +} - g_return_if_fail(req != NULL); +FbHttpValues * +fb_http_values_new(void) +{ + return g_hash_table_new_full(g_str_hash, fb_http_value_equal, + g_free, g_free); +} - va_start(ap, pair); - fb_http_tree_ins(req->params, pair, ap); - va_end(ap); +void +fb_http_values_free(FbHttpValues *values) +{ + g_hash_table_destroy(values); } -/** - * Implemented #b_event_handler for resending failed a #fb_http_req. - * - * @param data The user defined data, which is a #fb_http_req. - * @param fd The file descriptor. - * @param cond The #b_input_condition. - * - * @return FALSE to kill the timer. - **/ -static gboolean fb_http_req_done_error(gpointer data, gint fd, - b_input_condition cond) +void +fb_http_values_consume(FbHttpValues *values, FbHttpValues *consume) { - fb_http_req_t *req = data; + GHashTableIter iter; + gpointer key; + gpointer val; + + g_hash_table_iter_init(&iter, consume); - fb_http_req_send(req); - return FALSE; + while (g_hash_table_iter_next(&iter, &key, &val)) { + g_hash_table_iter_steal(&iter); + g_hash_table_replace(values, key, val); + } + + g_hash_table_destroy(consume); } -/** - * Processes all #fb_http_req by resending, queuing, and freeing. - * - * @param req The #fb_http_req. - **/ -static void fb_http_req_done(fb_http_req_t *req) -{ -#ifdef DEBUG_FACEBOOK - fb_http_req_debug(req, TRUE, req->header, req->body); -#endif /* DEBUG_FACEBOOK */ - - if (req->err != NULL) { - if (req->rsc < FB_HTTP_RESEND_MAX) { - fb_http_req_close(req, FALSE); - g_error_free(req->err); - req->err = NULL; - - req->toid = b_timeout_add(FB_HTTP_RESEND_TIMEOUT, - fb_http_req_done_error, req); - req->rsc++; +void +fb_http_values_parse(FbHttpValues *values, const gchar *data, gboolean isurl) +{ + const gchar *tail; + gchar *key; + gchar **params; + gchar *val; + guint i; + + g_return_if_fail(data != NULL); + + if (isurl) { + data = strchr(data, '?'); + + if (data++ == NULL) { return; } - g_prefix_error(&req->err, "HTTP: "); + tail = strchr(data, '#'); + + if (tail != NULL) { + data = g_strndup(data, tail - data); + } else { + data = g_strdup(data); + } + } + + params = g_strsplit(data, "&", 0); + + for (i = 0; params[i] != NULL; i++) { + key = params[i]; + val = strchr(params[i], '='); + + if (val == NULL) { + continue; + } + + *(val++) = 0; + key = g_uri_unescape_string(key, NULL); + val = g_uri_unescape_string(val, NULL); + g_hash_table_replace(values, key, val); + } + + if (isurl) { + g_free((gchar*) data); } - g_hash_table_remove(req->http->reqs, req); - fb_http_req_free(req); + g_strfreev(params); } -/** - * Implemented #http_input_function for all #fb_http_req. - * - * @param request The #http_request. - **/ -static void fb_http_req_cb(struct http_request *request) +gchar * +fb_http_values_str_cookies(FbHttpValues *values) { - fb_http_req_t *req = request->data; + GHashTableIter iter; + gchar *key; + gchar *val; + GString *ret; - /* Shortcut request elements */ - req->status = request->status_string; - req->scode = request->status_code; - req->header = request->reply_headers; - req->body = request->reply_body; - req->body_size = request->body_size; + ret = g_string_new(NULL); + g_hash_table_iter_init(&iter, values); - switch (req->scode) { - case 200: - case 301: - case 302: - case 303: - case 307: - break; + while (g_hash_table_iter_next(&iter, (gpointer) &key, (gpointer) &val)) { + if (val == NULL) { + val = ""; + } - default: - g_set_error(&req->err, FB_HTTP_ERROR, req->scode, "%s", req->status); + if (ret->len > 0) { + g_string_append(ret, "; "); + } + + g_string_append_uri_escaped(ret, key, NULL, TRUE); + g_string_append_c(ret, '='); + g_string_append_uri_escaped(ret, val, NULL, TRUE); } - req->request->flags |= FB_HTTP_CLIENT_FREED; - fb_http_req_done(req); + return g_string_free(ret, FALSE); } -/** - * Implemented #b_event_handler for handling a timed out #fb_http_req. - * - * @param data The user defined data, which is a #fb_http_req. - * @param fd The file descriptor. - * @param cond The #b_input_condition. - * - * @return FALSE to kill the timer. - **/ -static gboolean fb_http_req_send_timeout(gpointer data, gint fd, - b_input_condition cond) +gchar * +fb_http_values_str_headers(FbHttpValues *values) { - fb_http_req_t *req = data; + GHashTableIter iter; + gchar *key; + gchar *val; + GString *ret; + + ret = g_string_new(NULL); + g_hash_table_iter_init(&iter, values); + + while (g_hash_table_iter_next(&iter, (gpointer) &key, (gpointer) &val)) { + if (val == NULL) { + val = ""; + } - g_set_error(&req->err, FB_HTTP_ERROR, FB_HTTP_ERROR_TIMEOUT, - "Request timed out"); + g_string_append_printf(ret, "%s: %s\r\n", key, val); + } - req->toid = 0; - fb_http_req_done(req); - return FALSE; + return g_string_free(ret, FALSE); } -/** - * Assembles a #fb_http_req. The returned strings should be freed with - * #g_free() when no longer needed. - * - * @param req The #fb_http_req. - * @param hs The return location for the header string. - * @param ps The return location for the param string. - * @param fs The return location for the full string. - **/ -static void fb_http_req_asm(fb_http_req_t *req, gchar **hs, gchar **ps, - gchar **fs) -{ - GHashTableIter iter; - GString *hgs; - GString *pgs; - gchar *str; - gchar *key; - gchar *val; - - g_hash_table_iter_init(&iter, req->params); - pgs = g_string_sized_new(128); - - while (g_hash_table_iter_next(&iter, (gpointer*) &key, (gpointer*) &val)) { - if (val == NULL) - val = ""; +gchar * +fb_http_values_str_params(FbHttpValues *values, const gchar *url) +{ + GHashTableIter iter; + gchar *key; + gchar *val; + GString *ret; - key = fb_http_uri_escape(key); - val = fb_http_uri_escape(val); + ret = g_string_new(NULL); + g_hash_table_iter_init(&iter, values); - str = (pgs->len > 0) ? "&" : ""; - g_string_append_printf(pgs, "%s%s=%s", str, key, val); + while (g_hash_table_iter_next(&iter, (gpointer) &key, (gpointer) &val)) { + if (val == NULL) { + val = ""; + } - g_free(key); - g_free(val); + if (ret->len > 0) { + g_string_append_c(ret, '&'); + } + + g_string_append_uri_escaped(ret, key, NULL, TRUE); + g_string_append_c(ret, '='); + g_string_append_uri_escaped(ret, val, NULL, TRUE); } - if (g_hash_table_size(req->http->cookies) > 0) { - str = fb_http_cookies_str(req->http); - fb_http_req_headers_set(req, FB_HTTP_PAIR("Cookie", str), NULL); - g_free(str); + if (url != NULL) { + g_string_prepend_c(ret, '?'); + g_string_prepend(ret, url); } - if (req->flags & FB_HTTP_REQ_FLAG_POST) { - str = g_strdup_printf("%" G_GSIZE_FORMAT, pgs->len); + return g_string_free(ret, FALSE); +} - fb_http_req_headers_set(req, - FB_HTTP_PAIR("Content-Type", "application/" - "x-www-form-urlencoded"), - FB_HTTP_PAIR("Content-Length", str), - NULL - ); +gboolean +fb_http_values_remove(FbHttpValues *values, const gchar *name) +{ + return g_hash_table_remove(values, name); +} - g_free(str); - } +GList * +fb_http_values_get_keys(FbHttpValues *values) +{ + return g_hash_table_get_keys(values); +} - g_hash_table_iter_init(&iter, req->headers); - hgs = g_string_sized_new(128); +static const gchar * +fb_http_values_get(FbHttpValues *values, const gchar *name, GError **error) +{ + const gchar *ret; - while (g_hash_table_iter_next(&iter, (gpointer*) &key, (gpointer*) &val)) { - if (val == NULL) - val = ""; + ret = g_hash_table_lookup(values, name); - g_string_append_printf(hgs, "%s: %s\r\n", key, val); + if (ret == NULL) { + g_set_error(error, FB_HTTP_ERROR, FB_HTTP_ERROR_NOMATCH, + "No matches for %s", name); + return NULL; } - if (req->flags & FB_HTTP_REQ_FLAG_POST) { - *fs = g_strdup_printf("POST %s HTTP/1.1\r\n%s\r\n%s", - req->path, hgs->str, pgs->str); - } else { - *fs = g_strdup_printf("GET %s?%s HTTP/1.1\r\n%s\r\n", - req->path, pgs->str, hgs->str); + return ret; +} + +gboolean +fb_http_values_get_bool(FbHttpValues *values, const gchar *name, + GError **error) +{ + const gchar *val; + + val = fb_http_values_get(values, name, error); + + if (val == NULL) { + return FALSE; } - *hs = g_string_free(hgs, FALSE); - *ps = g_string_free(pgs, FALSE); + return bool2int((gchar *) name); } -/** - * Sends a #fb_http_req. - * - * @param req The #fb_http_req. - **/ -void fb_http_req_send(fb_http_req_t *req) +gdouble +fb_http_values_get_dbl(FbHttpValues *values, const gchar *name, + GError **error) { - gchar *str; - gchar *hs; - gchar *ps; + const gchar *val; - g_return_if_fail(req != NULL); + val = fb_http_values_get(values, name, error); - fb_http_req_asm(req, &hs, &ps, &str); + if (val == NULL) { + return 0.0; + } -#ifdef DEBUG_FACEBOOK - fb_http_req_debug(req, FALSE, hs, ps); -#endif /* DEBUG_FACEBOOK */ + return g_ascii_strtod(val, NULL); +} - req->request = http_dorequest(req->host, req->port, - (req->flags & FB_HTTP_REQ_FLAG_SSL), - str, fb_http_req_cb, req); - g_hash_table_add(req->http->reqs, req); +gint64 +fb_http_values_get_int(FbHttpValues *values, const gchar *name, + GError **error) +{ + const gchar *val; - g_free(hs); - g_free(ps); - g_free(str); + val = fb_http_values_get(values, name, error); - if (G_UNLIKELY(req->request == NULL)) { - g_set_error(&req->err, FB_HTTP_ERROR, FB_HTTP_ERROR_INIT, - "Failed to init request"); - fb_http_req_done(req); - return; + if (val == NULL) { + return 0; } - /* Prevent automatic redirection */ - req->request->redir_ttl = 0; + return g_ascii_strtoll(val, NULL, 10); +} - if (req->timeout > 0) { - req->toid = b_timeout_add(req->timeout, fb_http_req_send_timeout, - req); - } + +const gchar * +fb_http_values_get_str(FbHttpValues *values, const gchar *name, + GError **error) +{ + return fb_http_values_get(values, name, error); } -/** - * Escapes the characters of a string to make it URL safe. The returned - * string should be freed with #g_free() when no longer needed. - * - * @param unescaped The string. - * - * @return The escaped string or NULL on error. - **/ -gchar *fb_http_uri_escape(const gchar *unescaped) +gchar * +fb_http_values_dup_str(FbHttpValues *values, const gchar *name, + GError **error) { - gchar *ret; - gchar *str; + const gchar *str; + + str = fb_http_values_get_str(values, name, error); + return g_strdup(str); +} - g_return_val_if_fail(unescaped != NULL, NULL); +static void +fb_http_values_set(FbHttpValues *values, const gchar *name, gchar *value) +{ + gchar *key; - str = g_strndup(unescaped, (strlen(unescaped) * 3) + 1); - http_encode(str); + key = g_strdup(name); + g_hash_table_replace(values, key, value); +} - ret = g_strdup(str); - g_free(str); +void +fb_http_values_set_bool(FbHttpValues *values, const gchar *name, + gboolean value) +{ + gchar *val; - return ret; + val = g_strdup(value ? "true" : "false"); + fb_http_values_set(values, name, val); } -/** - * Unescapes the characters of a string to make it a normal string. The - * returned string should be freed with #g_free() when no longer needed. - * - * @param escaped The string. - * - * @return The unescaped string or NULL on error. - **/ -gchar *fb_http_uri_unescape(const gchar *escaped) +void +fb_http_values_set_dbl(FbHttpValues *values, const gchar *name, gdouble value) { - gchar *ret; - gchar *str; + gchar *val; - g_return_val_if_fail(escaped != NULL, NULL); + val = g_strdup_printf("%f", value); + fb_http_values_set(values, name, val); +} - str = g_strdup(escaped); - http_decode(str); +void +fb_http_values_set_int(FbHttpValues *values, const gchar *name, gint64 value) +{ + gchar *val; - ret = g_strdup(str); - g_free(str); + val = g_strdup_printf("%" G_GINT64_FORMAT, value); + fb_http_values_set(values, name, val); +} - return ret; +void +fb_http_values_set_str(FbHttpValues *values, const gchar *name, + const gchar *value) +{ + gchar *val; + + val = g_strdup(value); + fb_http_values_set(values, name, val); +} + +void +fb_http_values_set_strf(FbHttpValues *values, const gchar *name, + const gchar *format, ...) +{ + gchar *val; + va_list ap; + + va_start(ap, format); + val = g_strdup_vprintf(format, ap); + va_end(ap); + + fb_http_values_set(values, name, val); } diff --git a/facebook/facebook-http.h b/facebook/facebook-http.h index 6aa1176..1c07344 100644 --- a/facebook/facebook-http.h +++ b/facebook/facebook-http.h @@ -15,163 +15,538 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -/** @file **/ +#ifndef _FACEBOOK_HTTP_H_ +#define _FACEBOOK_HTTP_H_ -#ifndef _FACEBOOK_HTTP_H -#define _FACEBOOK_HTTP_H +/** + * SECTION:api + * @section_id: facebook-http + * @short_description: <filename>facebook-http.h</filename> + * @title: HTTP Client + * + * The HTTP client. + */ #include <glib.h> +#include <glib-object.h> #include <http_client.h> +#define FB_TYPE_HTTP (fb_http_get_type()) +#define FB_HTTP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), FB_TYPE_HTTP, FbHttp)) +#define FB_HTTP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), FB_TYPE_HTTP, FbHttpClass)) +#define FB_IS_HTTP(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), FB_TYPE_HTTP)) +#define FB_IS_HTTP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), FB_TYPE_HTTP)) +#define FB_HTTP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), FB_TYPE_HTTP, FbHttpClass)) -#define FB_HTTP_CLIENT_FREED (1 << 31) -#define FB_HTTP_RESEND_MAX 3 -#define FB_HTTP_RESEND_TIMEOUT 2000 - +#define FB_TYPE_HTTP_REQUEST (fb_http_request_get_type()) +#define FB_HTTP_REQUEST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), FB_TYPE_HTTP_REQUEST, FbHttpRequest)) +#define FB_HTTP_REQUEST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), FB_TYPE_HTTP_REQUEST, FbHttpRequestClass)) +#define FB_IS_HTTP_REQUEST(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), FB_TYPE_HTTP_REQUEST)) +#define FB_IS_HTTP_REQUEST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), FB_TYPE_HTTP_REQUEST)) +#define FB_HTTP_REQUEST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), FB_TYPE_HTTP_REQUEST, FbHttpRequestClass)) /** - * Creates a #fb_http_pair in-line. - * - * @param k The key. - * @param v The value. + * FB_HTTP_ERROR: * - * @return The resulting fb_http_pair. - **/ -#define FB_HTTP_PAIR(k, v) ((fb_http_pair_t *) &((fb_http_pair_t) {k, v})) - - -/** The #GError codes of #fb_http. **/ -typedef enum fb_http_error fb_http_error_t; - -/** The flags of #fb_http_req. **/ -typedef enum fb_http_req_flags fb_http_req_flags_t; - -/** The structure for managing #fb_http_req. **/ -typedef struct fb_http fb_http_t; - -/** The structure for key/value pairs of strings. **/ -typedef struct fb_http_pair fb_http_pair_t; + * The #GQuark of the domain of HTTP errors. + */ +#define FB_HTTP_ERROR fb_http_error_quark() -/** The structure for a #fb_http request. **/ -typedef struct fb_http_req fb_http_req_t; +typedef struct _FbHttp FbHttp; +typedef struct _FbHttpClass FbHttpClass; +typedef struct _FbHttpPrivate FbHttpPrivate; +typedef struct _FbHttpRequest FbHttpRequest; +typedef struct _FbHttpRequestClass FbHttpRequestClass; +typedef struct _FbHttpRequestPrivate FbHttpRequestPrivate; +/** + * FbHttpValues: + * + * Represents a set of key/value HTTP values. + */ +typedef GHashTable FbHttpValues; /** - * The type of callback for #fb_http_req operations. + * FbHttpFunc: + * @req: The #FbHttpRequest. + * @data: The user-defined data. * - * @param req The #fb_http_req. - * @param data The user defined data or NULL. - **/ -typedef void (*fb_http_func_t) (fb_http_req_t *req, gpointer data); + * The callback for HTTP requests. + */ +typedef void (*FbHttpFunc) (FbHttpRequest *req, gpointer data); +/** + * FbHttpError: + * @FB_HTTP_ERROR_SUCCESS: There is no error. + * @FB_HTTP_ERROR_INIT: The request failed to initialize. + * @FB_HTTP_ERROR_NOMATCH: The name does not match anything. + * + * The error codes for the #FB_HTTP_ERROR domain. + */ +typedef enum +{ + FB_HTTP_ERROR_SUCCESS = 0, + FB_HTTP_ERROR_INIT, + FB_HTTP_ERROR_NOMATCH +} FbHttpError; /** - * The #GError codes of #fb_http. - **/ -enum fb_http_error + * FbHttp: + * + * Represents an HTTP client. + */ +struct _FbHttp { - FB_HTTP_ERROR_CLOSED = 1, /** Closed **/ - FB_HTTP_ERROR_INIT, /** Initializing **/ - FB_HTTP_ERROR_TIMEOUT, /** Timeout **/ + /*< private >*/ + GObject parent; + FbHttpPrivate *priv; }; /** - * The flags of #fb_http_req. - **/ -enum fb_http_req_flags + * FbHttpClass: + * + * The base class for all #FbHttp's. + */ +struct _FbHttpClass { - FB_HTTP_REQ_FLAG_GET = 1 << 0, /** Use the GET method **/ - FB_HTTP_REQ_FLAG_POST = 1 << 1, /** Use the POST method **/ - FB_HTTP_REQ_FLAG_SSL = 1 << 2 /** Use encryption via SSL **/ + /*< private >*/ + GObjectClass parent_class; }; /** - * The structure for managing #fb_http_req. - **/ -struct fb_http + * FbHttpRequest: + * + * Represents an HTTP request. + */ +struct _FbHttpRequest { - gchar *agent; /** The agent. **/ - GHashTable *cookies; /** The #GHashTable of cookies. **/ - GHashTable *reqs; /** The #GHashTable of #fb_http_req. **/ + /*< private >*/ + GObject parent; + FbHttpRequestPrivate *priv; }; /** - * The structure for key/value pairs of strings. - **/ -struct fb_http_pair + * FbHttpRequestClass: + * + * The base class for all #FbHttpRequest's. + */ +struct _FbHttpRequestClass { - const gchar *key; /** The key. **/ - const gchar *val; /** The value. **/ + /*< private >*/ + GObjectClass parent_class; }; /** - * he structure for a #fb_http request. - **/ -struct fb_http_req -{ - fb_http_t *http; /** The #fb_http. **/ - fb_http_req_flags_t flags; /** The #fb_http_req_flags. **/ + * fb_http_get_type: + * + * Returns: The #GType for an #FbHttp. + */ +GType +fb_http_get_type(void); - gchar *host; /** The hostname. **/ - gint port; /** The port number. **/ - gchar *path; /** The pathname. **/ - gint timeout; /** The timeout. **/ +/** + * fb_http_request_get_type: + * + * Returns: The #GType for an #FbHttpRequest. + */ +GType +fb_http_request_get_type(void); - GHashTable *headers; /** The #GHashTable of headers. **/ - GHashTable *params; /** The #GHashTable of parameters. **/ +/** + * fb_http_error_quark: + * + * Gets the #GQuark of the domain of HTTP errors. + * + * Returns: The #GQuark of the domain. + */ +GQuark +fb_http_error_quark(void); - fb_http_func_t func; /** The user callback function or NULL. **/ - gpointer data; /** The user define data or NULL. **/ +/** + * fb_http_new: + * @agent: The User-Agent. + * + * Creates a new #FbHttp. The returned #FbHttp should be freed with + * #g_object_unref() when no longer needed. + * + * Returns: The new #FbHttp. + */ +FbHttp * +fb_http_new(const gchar *agent); - struct http_request *request; /** The underlying #http_request. **/ +/** + * fb_http_get_cookies: + * @http: The #FbHttp. + * + * Gets the #FbHttpValues for cookies from the #FbHttp. The returned + * #FbHttpValues should not be freed. + * + * Returns: The #FbHttpValues. + */ +FbHttpValues * +fb_http_get_cookies(FbHttp *http); - GError *err; /** The #GError or NULL. **/ - gchar *status; /** Shortcut to request->status_string. **/ - gint scode; /** Shortcut to request->status_code. **/ - gchar *header; /** Shortcut to request->reply_headers. **/ - gchar *body; /** Shortcut to request->reply_body. **/ - gint body_size; /** Shortcut to request->body_size. **/ +/** + * fb_http_close_requests: + * @http: The #FbHttp. + * + * Closes all active #FbHttpRequest from the #FbHttp. + */ +void +fb_http_close_requests(FbHttp *http); - gint toid; /** The event ID for the timeout. **/ - guint8 rsc; /** The resend count. **/ -}; +/** + * fb_http_cookies_parse_request: + * @http: The #FbHttp. + * @data: The string to parse. + * + * Parses and loads cookies from the #FbHttpRequest into the #FbHttp. + */ +void +fb_http_cookies_parse_request(FbHttp *http, FbHttpRequest *req); +/** + * fb_http_request_new: + * @http: The #FbHttp. + * @url: The url. + * @post: #TRUE for the POST, otherwise #FALSE for GET. + * @func: The #FbHttpFunc. + * @data: The user-defined data. + * + * Creates a new #FbHttpRequest. The returned #FbHttpRequest should be + * freed with #g_object_unref() when no longer needed. + * + * Returns: The new #FbHttpRequest. + */ +FbHttpRequest * +fb_http_request_new(FbHttp *http, const gchar *url, gboolean post, + FbHttpFunc func, gpointer data); -#define FB_HTTP_ERROR fb_http_error_quark() +/** + * fb_http_request_get_data: + * @req: The #FbHttpRequest. + * @code: The return location for size or #NULL. + * + * Gets the request data from the #FbHttpRequest. This should only be + * inside #FbHttpFunc passed to #fb_http_request_new(). + * + * Returns: The request data string. + */ +const gchar * +fb_http_request_get_data(FbHttpRequest *req, gsize *size); -GQuark fb_http_error_quark(void); +/** + * fb_http_request_get_headers: + * @req: The #FbHttpRequest. + * + * Gets the #FbHttpValues for headers from the #FbHttpRequest. The + * returned #FbHttpValues should not be freed. + * + * Returns: The #FbHttpValues. + */ +FbHttpValues * +fb_http_request_get_headers(FbHttpRequest *req); -fb_http_t *fb_http_new(const gchar *agent); +/** + * fb_http_request_get_params: + * @req: The #FbHttpRequest. + * + * Gets the #FbHttpValues for parameters from the #FbHttpRequest. The + * returned #FbHttpValues should not be freed. + * + * Returns: The #FbHttpValues. + */ +FbHttpValues * +fb_http_request_get_params(FbHttpRequest *req); -void fb_http_free_reqs(fb_http_t *http); +/** + * fb_http_request_get_status: + * @req: The #FbHttpRequest. + * @code: The return location for the status code or #NULL. + * + * Gets the request status from the #FbHttpRequest. This should only be + * inside #FbHttpFunc passed to #fb_http_request_new(). + * + * Returns: The status string. + */ +const gchar * +fb_http_request_get_status(FbHttpRequest *req, gint *code); -void fb_http_free(fb_http_t *http); +/** + * fb_http_request_take_error: + * @req: The #FbHttpRequest. + * + * Gets the #GError from the #FbHttpRequest. This should only be + * inside #FbHttpFunc passed to #fb_http_request_new(). The returned + * #GError should be freed with #g_error_free() when no longer needed. + * + * Returns: The #GError or #NULL. + */ +GError * +fb_http_request_take_error(FbHttpRequest *req); -void fb_http_cookies_set(fb_http_t *http, const fb_http_pair_t *pair, ...) - G_GNUC_NULL_TERMINATED; +/** + * fb_http_request_send: + * @req: The #FbHttpRequest. + * + * Sends the #FbHttpRequest to the remote server. + */ +void +fb_http_request_send(FbHttpRequest *req); -void fb_http_cookies_parse_req(fb_http_t *http, const fb_http_req_t *req); +/** + * fb_http_urlcmp: + * @url1: The first URL. + * @url2: The second URL. + * @protocol: #TRUE to match the protocols, otherwise #FALSE. + * + * Compares two URLs. This is more reliable than just comparing two URL + * strings, as it avoids casing in some areas, while not in others. It + * can also, optionally, ignore the matching of the URL protocol. + * + * Returns: #TRUE if the URLs match, otherwise #FALSE. + */ +gboolean +fb_http_urlcmp(const gchar *url1, const gchar *url2, gboolean protocol); -void fb_http_cookies_parse_str(fb_http_t *http, const gchar *data); +/** + * fb_http_values_new: + * + * Creates a new #FbHttpValues. The returned #FbHttpValues should be + * freed with #fb_http_values_free() when no longer needed. + * + * Returns: The new #FbHttpValues. + */ +FbHttpValues * +fb_http_values_new(void); -gchar *fb_http_cookies_str(fb_http_t *http); +/** + * fb_http_values_free: + * @values: The #FbHttpValues. + * + * Frees all memory used by the #FbHttpValues. + */ +void +fb_http_values_free(FbHttpValues *values); -fb_http_req_t *fb_http_req_new(fb_http_t *http, const gchar *host, - gint port, const gchar *path, - fb_http_func_t func, gpointer data); +/** + * fb_http_values_consume: + * @values: The #FbHttpValues. + * @consume: The #FbHttpValues to consume. + * + * Consumes another #FbHttpValues into the #FbHttpValues. This will + * overwrite any existing values. This will free the consumed + * #FbHttpValues. + */ +void +fb_http_values_consume(FbHttpValues *values, FbHttpValues *consume); -void fb_http_req_free(fb_http_req_t *req); +/** + * fb_http_values_parse: + * @values: The #FbHttpValues. + * @data: The data string. + * @isurl: TRUE if @data is a URL, otherwise FALSE. + * + * Parses and loads a parameter string into the #FbHttpValues. + */ +void +fb_http_values_parse(FbHttpValues *values, const gchar *data, gboolean isurl); -void fb_http_req_headers_set(fb_http_req_t *req, const fb_http_pair_t *pair, - ...) G_GNUC_NULL_TERMINATED; +/** + * fb_http_values_str_cookies: + * @values: The #FbHttpValues. + * + * Creates a cookie string for the Set-Cookie header. The returned + * string should be freed with #g_free() when no longer needed. + * + * Returns: The cookie string. + */ +gchar * +fb_http_values_str_cookies(FbHttpValues *values); -void fb_http_req_params_set(fb_http_req_t *req, const fb_http_pair_t *pair, - ...) G_GNUC_NULL_TERMINATED; +/** + * fb_http_values_str_headers: + * @values: The #FbHttpValues. + * + * Creates a header string for a raw HTTP request. The returned string + * should be freed with #g_free() when no longer needed. + * + * Returns: The header string. + */ +gchar * +fb_http_values_str_headers(FbHttpValues *values); + +/** + * fb_http_values_str_params: + * @values: The #FbHttpValues. + * @url: The URL or #NULL. + * + * Creates a parameter string for a raw HTTP request. If @url is + * non-#NULL, then the parameters are appended to @url. The returned + * string should be freed with #g_free() when no longer needed. + * + * Returns: The parameter string. + */ +gchar * +fb_http_values_str_params(FbHttpValues *values, const gchar *url); -void fb_http_req_send(fb_http_req_t *req); +/** + * fb_http_values_remove: + * @values: The #FbHttpValues. + * @name: The value name. + * + * Removes a value from the #FbHttpValues. + * + * Returns: #TRUE if the value was removed, otherwise #FALSE. + */ +gboolean +fb_http_values_remove(FbHttpValues *values, const gchar *name); -gchar *fb_http_uri_escape(const gchar *unescaped); +/** + * fb_http_values_get_keys: + * @values: The #FbHttpValues. + * + * Gets a #GList of keys from the #FbHttpValues. + * + * Returns: The #GList of keys. + */ +GList * +fb_http_values_get_keys(FbHttpValues *values); -gchar *fb_http_uri_unescape(const gchar *escaped); +/** + * fb_http_values_get_bool: + * @values: The #FbHttpValues. + * @name: The value name. + * @error: The return location for the #GError, or #NULL. + * + * Gets a boolean value from the #FbHttpValues. This optionally assigns + * an appropriate #GError upon failure. + * + * Return: The boolean value. + */ +gboolean +fb_http_values_get_bool(FbHttpValues *values, const gchar *name, + GError **error); + +/** + * fb_http_values_get_dbl: + * @values: The #FbHttpValues. + * @name: The value name. + * @error: The return location for the #GError, or #NULL. + * + * Gets a floating point value from the #FbHttpValues. This optionally + * assigns an appropriate #GError upon failure. + * + * Return: The floating point value. + */ +gdouble +fb_http_values_get_dbl(FbHttpValues *values, const gchar *name, + GError **error); + +/** + * fb_http_values_get_int: + * @values: The #FbHttpValues. + * @name: The value name. + * @error: The return location for the #GError, or #NULL. + * + * Gets an integer value from the #FbHttpValues. This optionally + * assigns an appropriate #GError upon failure. + * + * Return: The integer value. + */ +gint64 +fb_http_values_get_int(FbHttpValues *values, const gchar *name, + GError **error); + +/** + * fb_http_values_get_str: + * @values: The #FbHttpValues. + * @name: The value name. + * @error: The return location for the #GError, or #NULL. + * + * Gets a string value from the #FbHttpValues. This optionally assigns + * an appropriate #GError upon failure. + * + * Return: The string value. + */ +const gchar * +fb_http_values_get_str(FbHttpValues *values, const gchar *name, + GError **error); + +/** + * fb_http_values_dup_str: + * @values: The #FbHttpValues. + * @name: The value name. + * @error: The return location for the #GError, or #NULL. + * + * Gets a duplicated string value from the #FbHttpValues. This + * optionally assigns an appropriate #GError upon failure. The returned + * string should be freed with #g_free() when no longer needed. + * + * Return: The duplicated string value. + */ +gchar * +fb_http_values_dup_str(FbHttpValues *values, const gchar *name, + GError **error); + +/** + * fb_http_values_set_bool: + * @values: The #FbHttpValues. + * @name: The value name. + * @value: The value. + * + * Sets a boolean value to the #FbHttpValues. + */ +void +fb_http_values_set_bool(FbHttpValues *values, const gchar *name, + gboolean value); + +/** + * fb_http_values_set_dbl: + * @values: The #FbHttpValues. + * @name: The value name. + * @value: The value. + * + * Sets a floating point value to the #FbHttpValues. + */ +void +fb_http_values_set_dbl(FbHttpValues *values, const gchar *name, gdouble value); + +/** + * fb_http_values_set_int: + * @values: The #FbHttpValues. + * @name: The value name. + * @value: The value. + * + * Sets an integer value to the #FbHttpValues. + */ +void +fb_http_values_set_int(FbHttpValues *values, const gchar *name, gint64 value); + +/** + * fb_http_values_set_str: + * @values: The #FbHttpValues. + * @name: The value name. + * @value: The value. + * + * Sets a string value to the #FbHttpValues. + */ +void +fb_http_values_set_str(FbHttpValues *values, const gchar *name, + const gchar *value); + +/** + * fb_http_values_set_strf: + * @values: The #FbHttpValues. + * @name: The value name. + * @format: The format string literal. + * @...: The arguments for @format. + * + * Sets a formatted string value to the #FbHttpValues. + */ +void +fb_http_values_set_strf(FbHttpValues *values, const gchar *name, + const gchar *format, ...) + G_GNUC_PRINTF(3, 4); -#endif /* _FACEBOOK_HTTP_H */ +#endif /* _FACEBOOK_HTTP_H_ */ diff --git a/facebook/facebook-id.h b/facebook/facebook-id.h index 76a671c..66161bb 100644 --- a/facebook/facebook-id.h +++ b/facebook/facebook-id.h @@ -15,43 +15,113 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -/** @file **/ +#ifndef _FACEBOOK_ID_H_ +#define _FACEBOOK_ID_H_ -#ifndef _FACEBOOK_ID_H -#define _FACEBOOK_ID_H +/** + * SECTION:id + * @section_id: facebook-id + * @short_description: <filename>facebook-id.h</filename> + * @title: Facebook Identifier + * + * The Facebook identifier utilities. + */ #include <glib.h> #include <glib/gprintf.h> -#define FB_ID_CONSTANT(v) G_GINT64_CONSTANT(v) -#define FB_ID_FORMAT G_GINT64_FORMAT -#define FB_ID_MODIFIER G_GINT64_MODIFIER -#define FB_ID_STRMAX 21 -#define fb_id_hash g_int64_hash -#define fb_id_equal g_int64_equal +#include "facebook-util.h" + +/** + * FB_ID_FORMAT: + * + * The format specifier for printing and scanning an #FbId. + */ +#define FB_ID_FORMAT G_GINT64_FORMAT + +/** + * FB_ID_MODIFIER: + * + * The length modifier for printing an #FbId. + */ +#define FB_ID_MODIFIER G_GINT64_MODIFIER + +/** + * FB_ID_STRMAX: + * + * The maximum length, including a null-terminating character, of the + * string representation of an #FbId. + */ +#define FB_ID_STRMAX 21 + +/** + * FB_TYPE_ID: + * + * The #GType of an #FbId. + */ +#define FB_TYPE_ID G_TYPE_INT64 + +/** + * FB_ID_CONSTANT: + * @v: The value. + * + * Inserts a literal #FbId into source code. + * + * Return: The literal #FbId value. + */ +#define FB_ID_CONSTANT(v) G_GINT64_CONSTANT(v) + +/** + * FB_ID_FROM_STR: + * @s: The string value. + * + * Converts a string to an #FbId. + * + * Return: The converted #FbId value. + */ +#define FB_ID_FROM_STR(s) g_ascii_strtoll(s, NULL, 10) /** - * Converts a string to a #fb_id. + * FB_ID_IS_STR: + * @s: The string value. * - * @param s The string. + * Determines if a string is an #FbId. * - * @return The resulting #fb_id. - **/ -#define FB_ID_FROM_STR(s) \ - g_ascii_strtoll(s, NULL, 10) + * Return: #TRUE if the string is an #FbId, otherwise #FALSE. + */ +#define FB_ID_IS_STR(s) fb_util_str_is(s, G_ASCII_DIGIT) /** - * Converts a #f_uid to a string. The buffer should be at least - * #FB_ID_STRMAX in length. + * FB_ID_TO_STR: + * @i: The #FbId. + * @s: The string buffer. * - * @param i The #fb_id. - * @param s The string buffer. - **/ -#define FB_ID_TO_STR(i, s) \ - g_sprintf(s, "%" FB_ID_FORMAT, (fb_id_t) i) + * Converts an #FbId to a string. The buffer should be at least the + * size of #FB_ID_STRMAX. + * + * Return: The converted string value. + */ +#define FB_ID_TO_STR(i, s) g_sprintf(s, "%" FB_ID_FORMAT, (FbId) i) +/** + * fb_id_equal: + * + * Compares the values of two #FbId's for equality. See #g_int64_equal. + */ +#define fb_id_equal g_int64_equal + +/** + * fb_id_hash: + * + * Converts a pointer to a #FbId hash value. See #g_int64_hash. + */ +#define fb_id_hash g_int64_hash -/** The 64-bit Facebook identifier. **/ -typedef gint64 fb_id_t; +/** + * FbId: + * + * Represents a numeric Facebook identifier. + */ +typedef gint64 FbId; -#endif /* _FACEBOOK_ID_H */ +#endif /* _FACEBOOK_ID_H_ */ diff --git a/facebook/facebook-json.c b/facebook/facebook-json.c index 3be7de7..7272b6c 100644 --- a/facebook/facebook-json.c +++ b/facebook/facebook-json.c @@ -15,343 +15,640 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include <inttypes.h> +#include <stdarg.h> #include <string.h> #include "facebook-json.h" +#include "facebook-util.h" -/** - * Gets the error domain for the JSON parser. - * - * @return The #GQuark of the error domain. - **/ -GQuark fb_json_error_quark(void) +typedef struct _FbJsonValue FbJsonValue; + +struct _FbJsonValue +{ + const gchar *expr; + FbJsonType type; + gboolean required; + GValue value; +}; + +struct _FbJsonValuesPrivate +{ + JsonNode *root; + GQueue *queue; + GList *next; + + gboolean isarray; + JsonArray *array; + guint index; + + GError *error; +}; + +G_DEFINE_TYPE(FbJsonValues, fb_json_values, G_TYPE_OBJECT); + +static void +fb_json_values_dispose(GObject *obj) +{ + FbJsonValue *value; + FbJsonValuesPrivate *priv = FB_JSON_VALUES(obj)->priv; + + while (!g_queue_is_empty(priv->queue)) { + value = g_queue_pop_head(priv->queue); + + if (G_IS_VALUE(&value->value)) { + g_value_unset(&value->value); + } + + g_free(value); + } + + if (priv->array != NULL) { + json_array_unref(priv->array); + } + + if (priv->error != NULL) { + g_error_free(priv->error); + } + + g_queue_free(priv->queue); +} + +static void +fb_json_values_class_init(FbJsonValuesClass *klass) +{ + GObjectClass *gklass = G_OBJECT_CLASS(klass); + + gklass->dispose = fb_json_values_dispose; + g_type_class_add_private(klass, sizeof (FbJsonValuesPrivate)); +} + +static void +fb_json_values_init(FbJsonValues *values) +{ + FbJsonValuesPrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(values, FB_TYPE_JSON_VALUES, + FbJsonValuesPrivate); + values->priv = priv; + + priv->queue = g_queue_new(); +} + +GQuark +fb_json_error_quark(void) { - static GQuark q; + static GQuark q = 0; - if (G_UNLIKELY(q == 0)) + if (G_UNLIKELY(q == 0)) { q = g_quark_from_static_string("fb-json-error-quark"); + } return q; } -/** - * Creates a new #json_value from JSON data. The returned #json_value - * should be freed with #json_value_free() when no longer needed. - * - * @param data The JSON data. - * @param length The length of the JSON data. - * @param err The return location for a GError or NULL. - * - * @return The #json_value or NULL on error. - **/ -json_value *fb_json_new(const gchar *data, gsize length, GError **err) +JsonBuilder * +fb_json_bldr_new(JsonNodeType type) { - json_value *json; - json_settings js; - gchar *estr; - gchar *dstr; - gchar *escaped; + JsonBuilder *bldr; - memset(&js, 0, sizeof js); - estr = g_new0(gchar, json_error_max); - json = json_parse_ex(&js, data, length, estr); + bldr = json_builder_new(); - if ((json != NULL) && (strlen(estr) < 1)) { - g_free(estr); - return json; + switch (type) { + case JSON_NODE_ARRAY: + fb_json_bldr_arr_begin(bldr, NULL); + break; + + case JSON_NODE_OBJECT: + fb_json_bldr_obj_begin(bldr, NULL); + break; + + default: + break; } - /* Ensure it's null-terminated before passing it to g_strescape() */ - dstr = g_strndup(data, MIN(length, 400)); - escaped = g_strescape(dstr, "\""); + return bldr; +} - g_set_error(err, FB_JSON_ERROR, FB_JSON_ERROR_PARSER, - "Parser: %s\nJSON len=%zd: %s", estr, length, escaped); +gchar * +fb_json_bldr_close(JsonBuilder *bldr, JsonNodeType type, gsize *size) +{ + gchar *ret; + JsonGenerator *genr; + JsonNode *root; + + switch (type) { + case JSON_NODE_ARRAY: + fb_json_bldr_arr_end(bldr); + break; - g_free(dstr); - g_free(escaped); + case JSON_NODE_OBJECT: + fb_json_bldr_obj_end(bldr); + break; - g_free(estr); - return NULL; + default: + break; + } + + genr = json_generator_new(); + root = json_builder_get_root(bldr); + + json_generator_set_root(genr, root); + ret = json_generator_to_data(genr, size); + + json_node_free(root); + g_object_unref(genr); + g_object_unref(bldr); + + return ret; } -/** - * Gets the string representation of a #json_value. The returned string - * should be freed with #g_free() when no longer needed. - * - * @param json The #json_value. - * - * @return The resulting string, or NULL on error. - **/ -gchar *fb_json_valstr(const json_value *json) +void +fb_json_bldr_arr_begin(JsonBuilder *bldr, const gchar *name) { - g_return_val_if_fail(json != NULL, NULL); + if (name != NULL) { + json_builder_set_member_name(bldr, name); + } - switch (json->type) { - case json_integer: - return g_strdup_printf("%" PRId64, json->u.integer); + json_builder_begin_array(bldr); +} - case json_double: - return g_strdup_printf("%f", json->u.dbl); +void +fb_json_bldr_arr_end(JsonBuilder *bldr) +{ + json_builder_end_array(bldr); +} - case json_string: - return g_strdup(json->u.string.ptr); +void +fb_json_bldr_obj_begin(JsonBuilder *bldr, const gchar *name) +{ + if (name != NULL) { + json_builder_set_member_name(bldr, name); + } - case json_boolean: - return g_strdup(json->u.boolean ? "true" : "false"); + json_builder_begin_object(bldr); +} - case json_null: - return g_strdup("null"); +void +fb_json_bldr_obj_end(JsonBuilder *bldr) +{ + json_builder_end_object(bldr); +} - default: - return NULL; +void +fb_json_bldr_add_bool(JsonBuilder *bldr, const gchar *name, gboolean value) +{ + if (name != NULL) { + json_builder_set_member_name(bldr, name); } + + json_builder_add_boolean_value(bldr, value); } -/** - * Gets a #json_value by name from a parent #json_value. - * - * @param json The #json_value. - * @param name The name. - * @param type The #json_type. - * - * @return The json_value if found, otherwise NULL. - **/ -json_value *fb_json_val(const json_value *json, const gchar *name, - json_type type) +void +fb_json_bldr_add_dbl(JsonBuilder *bldr, const gchar *name, gdouble value) { - json_value *val; + if (name != NULL) { + json_builder_set_member_name(bldr, name); + } - if (!fb_json_val_chk(json, name, type, &val)) - return NULL; + json_builder_add_double_value(bldr, value); +} - return val; +void +fb_json_bldr_add_int(JsonBuilder *bldr, const gchar *name, gint64 value) +{ + if (name != NULL) { + json_builder_set_member_name(bldr, name); + } + + json_builder_add_int_value(bldr, value); } -/** - * Gets a #json_value by name from a parent #json_value, and checks - * for its existence and type. - * - * @param json The #json_value. - * @param name The name. - * @param type The #json_type. - * @param val The return location for the value. - * - * @return TRUE if the value was found, or FALSE on error. - **/ -gboolean fb_json_val_chk(const json_value *json, const gchar *name, - json_type type, json_value **val) +void +fb_json_bldr_add_str(JsonBuilder *bldr, const gchar *name, const gchar *value) { - g_return_val_if_fail(json != NULL, FALSE); - g_return_val_if_fail(name != NULL, FALSE); - g_return_val_if_fail(val != NULL, FALSE); + if (name != NULL) { + json_builder_set_member_name(bldr, name); + } - *val = json_o_get(json, name); + json_builder_add_string_value(bldr, value); +} - if ((*val == NULL) || ((*val)->type != type)) { - *val = NULL; - return FALSE; +void +fb_json_bldr_add_strf(JsonBuilder *bldr, const gchar *name, + const gchar *format, ...) +{ + gchar *value; + va_list ap; + + va_start(ap, format); + value = g_strdup_vprintf(format, ap); + va_end(ap); + + fb_json_bldr_add_str(bldr, name, value); + g_free(value); +} + +JsonNode * +fb_json_node_new(const gchar *data, gssize size, GError **error) +{ + JsonNode *root; + JsonParser *prsr; + + prsr = json_parser_new(); + + if (!json_parser_load_from_data(prsr, data, size, error)) { + g_object_unref(prsr); + return NULL; } - return TRUE; + root = json_parser_get_root(prsr); + root = json_node_copy(root); + + g_object_unref(prsr); + return root; } -/** - * Gets an array by name from a parent #json_value. - * - * @param json The #json_value. - * @param name The name. - * - * @return The #json_value if found, otherwise NULL. - **/ -json_value *fb_json_array(const json_value *json, const gchar *name) +JsonNode * +fb_json_node_get(JsonNode *root, const gchar *expr, GError **error) { - json_value *val; + GError *err = NULL; + guint size; + JsonArray *rslt; + JsonNode *node; + JsonNode *ret; + + node = json_path_query(expr, root, &err); + + if (err != NULL) { + g_propagate_error(error, err); + json_node_free(node); + return NULL; + } + + rslt = json_node_get_array(node); + size = json_array_get_length(rslt); + + if (size < 1) { + g_set_error(error, FB_JSON_ERROR, FB_JSON_ERROR_NOMATCH, + "No matches for %s", expr); + json_node_free(node); + return NULL; + } - if (!fb_json_array_chk(json, name, &val)) + if (size > 1) { + g_set_error(error, FB_JSON_ERROR, FB_JSON_ERROR_AMBIGUOUS, + "Ambiguous matches for %s", expr); + json_node_free(node); return NULL; + } - return val; + if (json_array_get_null_element(rslt, 0)) { + g_set_error(error, FB_JSON_ERROR, FB_JSON_ERROR_NULL, + "Null value for %s", expr); + json_node_free(node); + return NULL; + } + + ret = json_array_dup_element(rslt, 0); + json_node_free(node); + return ret; } -/** - * Gets an array by name from a parent #json_value, and checks for its - * existence and type. - * - * @param json The #json_value. - * @param name The name. - * @param type The #json_type. - * @param val The return location for the value. - * - * @return TRUE if the value was found, or FALSE on error. - **/ -gboolean fb_json_array_chk(const json_value *json, const gchar *name, - json_value **val) +JsonNode * +fb_json_node_get_nth(JsonNode *root, guint n) { - return fb_json_val_chk(json, name, json_array, val); + GList *vals; + JsonNode *ret; + JsonObject *obj; + + obj = json_node_get_object(root); + vals = json_object_get_values(obj); + ret = g_list_nth_data(vals, n); + + g_list_free(vals); + return ret; } -/** - * Gets a boolean value by name from a parent #json_value. - * - * @param json The #json_value. - * @param name The name. - * - * @return The boolean value if found, otherwise FALSE. - **/ -gboolean fb_json_bool(const json_value *json, const gchar *name) +JsonArray * +fb_json_node_get_arr(JsonNode *root, const gchar *expr, GError **error) { - gboolean val; + JsonArray *ret; + JsonNode *rslt; - if (!fb_json_bool_chk(json, name, &val)) - return FALSE; + rslt = fb_json_node_get(root, expr, error); - return val; + if (rslt == NULL) { + return NULL; + } + + ret = json_node_dup_array(rslt); + json_node_free(rslt); + return ret; } -/** - * Gets a boolean value by name from a parent #json_value, and checks - * for its existence and type. - * - * @param json The #json_value. - * @param name The name. - * @param val The return location for the value. - * - * @return The boolean value if found, otherwise FALSE. - **/ -gboolean fb_json_bool_chk(const json_value *json, const gchar *name, - gboolean *val) +gboolean +fb_json_node_get_bool(JsonNode *root, const gchar *expr, GError **error) { - json_value *jv; + gboolean ret; + JsonNode *rslt; - g_return_val_if_fail(val != NULL, FALSE); + rslt = fb_json_node_get(root, expr, error); - if (!fb_json_val_chk(json, name, json_boolean, &jv)) { - *val = FALSE; + if (rslt == NULL) { return FALSE; } - *val = jv->u.boolean; - return TRUE; + ret = json_node_get_boolean(rslt); + json_node_free(rslt); + return ret; } -/** - * Gets a integer value by name from a parent #json_value. - * - * @param json The #json_value. - * @param name The name. - * - * @return The integer value if found, otherwise 0. - **/ -gint64 fb_json_int(const json_value *json, const gchar *name) +gdouble +fb_json_node_get_dbl(JsonNode *root, const gchar *expr, GError **error) { - gint64 val; + gdouble ret; + JsonNode *rslt; - if (!fb_json_int_chk(json, name, &val)) - return 0; + rslt = fb_json_node_get(root, expr, error); + + if (rslt == NULL) { + return 0.0; + } - return val; + ret = json_node_get_double(rslt); + json_node_free(rslt); + return ret; } -/** - * Gets a integer value by name from a parent #json_value, and checks - * for its existence and type. - * - * @param json The #json_value. - * @param name The name. - * @param val The return location for the value. - * - * @return TRUE if the value was found, or FALSE on error. - **/ -gboolean fb_json_int_chk(const json_value *json, const gchar *name, - gint64 *val) +gint64 +fb_json_node_get_int(JsonNode *root, const gchar *expr, GError **error) { - json_value *jv; + gint64 ret; + JsonNode *rslt; - g_return_val_if_fail(val != NULL, FALSE); + rslt = fb_json_node_get(root, expr, error); - if (!fb_json_val_chk(json, name, json_integer, &jv)) { - *val = 0; - return FALSE; + if (rslt == NULL) { + return 0; } - *val = jv->u.integer; - return TRUE; + ret = json_node_get_int(rslt); + json_node_free(rslt); + return ret; } -/** - * Gets a string value by name from a parent #json_value. - * - * @param json The #json_value. - * @param name The name. - * - * @return The string value if found, otherwise NULL. - **/ -const gchar *fb_json_str(const json_value *json, const gchar *name) +gchar * +fb_json_node_get_str(JsonNode *root, const gchar *expr, GError **error) { - const gchar *val; + gchar *ret; + JsonNode *rslt; + + rslt = fb_json_node_get(root, expr, error); - if (!fb_json_str_chk(json, name, &val)) + if (rslt == NULL) { return NULL; + } - return val; + ret = json_node_dup_string(rslt); + json_node_free(rslt); + return ret; } -/** - * Gets a string value by name from a parent #json_value, and checks - * for its existence and type. - * - * @param json The #json_value. - * @param name The name. - * @param val The return location for the value. - * - * @return TRUE if the value was found, or FALSE on error. - **/ -gboolean fb_json_str_chk(const json_value *json, const gchar *name, - const gchar **val) +FbJsonValues * +fb_json_values_new(JsonNode *root) { - json_value *jv; + FbJsonValues *values; + FbJsonValuesPrivate *priv; - g_return_val_if_fail(val != NULL, FALSE); + g_return_val_if_fail(root != NULL, NULL); - if (!fb_json_val_chk(json, name, json_string, &jv)) { - *val = NULL; - return FALSE; + values = g_object_new(FB_TYPE_JSON_VALUES, NULL); + priv = values->priv; + priv->root = root; + + return values; +} + +void +fb_json_values_add(FbJsonValues *values, FbJsonType type, gboolean required, + const gchar *expr) +{ + FbJsonValue *value; + FbJsonValuesPrivate *priv; + + g_return_if_fail(values != NULL); + g_return_if_fail(expr != NULL); + priv = values->priv; + + value = g_new0(FbJsonValue, 1); + value->expr = expr; + value->type = type; + value->required = required; + + g_queue_push_tail(priv->queue, value); +} + +JsonNode * +fb_json_values_get_root(FbJsonValues *values) +{ + FbJsonValuesPrivate *priv; + guint index; + + g_return_val_if_fail(values != NULL, NULL); + priv = values->priv; + + if (priv->array == NULL) { + return priv->root; } - *val = jv->u.string.ptr; - return TRUE; + g_return_val_if_fail(priv->index > 0, NULL); + index = priv->index - 1; + + if (json_array_get_length(priv->array) <= index) { + return NULL; + } + + return json_array_get_element(priv->array, index); } -/** - * Backslash-escapes a string to make it safe for json. The returned string - * should be freed with #g_free() when no longer needed. - * - * @param str The string to escape. - * - * @return The resulting string, or NULL on error. - **/ -gchar *fb_json_str_escape(const gchar *str) +void +fb_json_values_set_array(FbJsonValues *values, gboolean required, + const gchar *expr) { - GString *out; - guint i; + FbJsonValuesPrivate *priv; + + g_return_if_fail(values != NULL); + priv = values->priv; + + priv->array = fb_json_node_get_arr(priv->root, expr, &priv->error); + priv->isarray = TRUE; + + if ((priv->error != NULL) && !required) { + g_clear_error(&priv->error); + } +} + +gboolean +fb_json_values_update(FbJsonValues *values, GError **error) +{ + FbJsonValue *value; + FbJsonValuesPrivate *priv; + GError *err = NULL; + GList *l; + GType type; + JsonNode *root; + JsonNode *node; + + g_return_val_if_fail(values != NULL, FALSE); + priv = values->priv; + + if (G_UNLIKELY(priv->error != NULL)) { + g_propagate_error(error, priv->error); + priv->error = NULL; + return FALSE; + } + + if (priv->isarray) { + if ((priv->array == NULL) || + (json_array_get_length(priv->array) <= priv->index)) + { + return FALSE; + } + + root = json_array_get_element(priv->array, priv->index++); + } else { + root = priv->root; + } + + g_return_val_if_fail(root != NULL, FALSE); - g_return_val_if_fail(str != NULL, NULL); + for (l = priv->queue->head; l != NULL; l = l->next) { + value = l->data; + node = fb_json_node_get(root, value->expr, &err); + + if (G_IS_VALUE(&value->value)) { + g_value_unset(&value->value); + } - /* let's overallocate a bit */ - out = g_string_sized_new(strlen(str) * 2); + if (err != NULL) { + json_node_free(node); - for (i = 0; str[i] != '\0'; i++) { - if ((str[i] > 0) && (str[i] < 0x20)) { - g_string_append_printf(out, "\\u%04x", str[i]); + if (value->required) { + g_propagate_error(error, err); + return FALSE; + } + + g_clear_error(&err); continue; } - if ((str[i] == '"') || (str[i] == '\\')) { - g_string_append_c(out, '\\'); + + type = json_node_get_value_type(node); + + if (G_UNLIKELY(type != value->type)) { + g_set_error(error, FB_JSON_ERROR, FB_JSON_ERROR_TYPE, + "Expected a %s but got a %s for %s", + g_type_name(value->type), + g_type_name(type), + value->expr); + json_node_free(node); + return FALSE; } - g_string_append_c(out, str[i]); + + json_node_get_value(node, &value->value); + json_node_free(node); + } + + priv->next = priv->queue->head; + return TRUE; +} + +const GValue * +fb_json_values_next(FbJsonValues *values) +{ + FbJsonValue *value; + FbJsonValuesPrivate *priv; + + g_return_val_if_fail(values != NULL, NULL); + priv = values->priv; + + g_return_val_if_fail(priv->next != NULL, NULL); + value = priv->next->data; + priv->next = priv->next->next; + + if (!G_IS_VALUE(&value->value)) { + return NULL; + } + + return &value->value; +} + +gboolean +fb_json_values_next_bool(FbJsonValues *values, gboolean defval) +{ + const GValue *value; + + value = fb_json_values_next(values); + + if (G_UNLIKELY(value == NULL)) { + return defval; + } + + return g_value_get_boolean(value); +} + +gdouble +fb_json_values_next_dbl(FbJsonValues *values, gdouble defval) +{ + const GValue *value; + + value = fb_json_values_next(values); + + if (G_UNLIKELY(value == NULL)) { + return defval; + } + + return g_value_get_double(value); +} + +gint64 +fb_json_values_next_int(FbJsonValues *values, gint64 defval) +{ + const GValue *value; + + value = fb_json_values_next(values); + + if (G_UNLIKELY(value == NULL)) { + return defval; + } + + return g_value_get_int64(value); +} + +const gchar * +fb_json_values_next_str(FbJsonValues *values, const gchar *defval) +{ + const GValue *value; + + value = fb_json_values_next(values); + + if (G_UNLIKELY(value == NULL)) { + return defval; + } + + return g_value_get_string(value); +} + +gchar * +fb_json_values_next_str_dup(FbJsonValues *values, const gchar *defval) +{ + const GValue *value; + + value = fb_json_values_next(values); + + if (G_UNLIKELY(value == NULL)) { + return g_strdup(defval); } - return g_string_free(out, FALSE); + return g_value_dup_string(value); } diff --git a/facebook/facebook-json.h b/facebook/facebook-json.h index 01d7953..4ca5f61 100644 --- a/facebook/facebook-json.h +++ b/facebook/facebook-json.h @@ -15,62 +15,500 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -/** @file **/ +#ifndef _FACEBOOK_JSON_H_ +#define _FACEBOOK_JSON_H_ -#ifndef _FACEBOOK_JSON_H -#define _FACEBOOK_JSON_H +/** + * SECTION:json + * @section_id: facebook-json + * @short_description: <filename>facebook-json.h</filename> + * @title: JSON Utilities + * + * The JSON utilities. + */ #include <glib.h> -#include <json_util.h> +#include <glib-object.h> +#include <json-glib/json-glib.h> + +#define FB_TYPE_JSON_VALUES (fb_json_values_get_type()) +#define FB_JSON_VALUES(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), FB_TYPE_JSON_VALUES, FbJsonValues)) +#define FB_JSON_VALUES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), FB_TYPE_JSON_VALUES, FbJsonValuesClass)) +#define FB_IS_JSON_VALUES(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), FB_TYPE_JSON_VALUES)) +#define FB_IS_JSON_VALUES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), FB_TYPE_JSON_VALUES)) +#define FB_JSON_VALUES_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), FB_TYPE_JSON_VALUES, FbJsonValuesClass)) + +/** + * FB_JSON_ERROR: + * + * The #GQuark of the domain of JSON errors. + */ +#define FB_JSON_ERROR fb_json_error_quark() +typedef struct _FbJsonValues FbJsonValues; +typedef struct _FbJsonValuesClass FbJsonValuesClass; +typedef struct _FbJsonValuesPrivate FbJsonValuesPrivate; -/** The #GError codes of the JSON parser. **/ -typedef enum fb_json_error fb_json_error_t; +/** + * FbJsonError: + * @FB_JSON_ERROR_SUCCESS: There is no error. + * @FB_JSON_ERROR_AMBIGUOUS: The node has ambiguous matches. + * @FB_JSON_ERROR_GENERAL: General failure. + * @FB_JSON_ERROR_NOMATCH: The node does not match anything. + * @FB_JSON_ERROR_NULL: The node is of type NULL. + * @FB_JSON_ERROR_TYPE: The node has an unexpected type. + * + * The error codes for the #FB_JSON_ERROR domain. + */ +typedef enum +{ + FB_JSON_ERROR_SUCCESS = 0, + FB_JSON_ERROR_AMBIGUOUS, + FB_JSON_ERROR_GENERAL, + FB_JSON_ERROR_NOMATCH, + FB_JSON_ERROR_NULL, + FB_JSON_ERROR_TYPE +} FbJsonError; +/** + * FbJsonType: + * @FB_JSON_TYPE_NULL: An unknown value. + * @FB_JSON_TYPE_BOOL: A boolean (#TRUE or #FALSE). + * @FB_JSON_TYPE_DBL: A floating point number. + * @FB_JSON_TYPE_INT: A signed integer. + * @FB_JSON_TYPE_STR: A string. + * + * The JSON data types. + */ +typedef enum +{ + FB_JSON_TYPE_NULL = 0, + FB_JSON_TYPE_BOOL = G_TYPE_BOOLEAN, + FB_JSON_TYPE_DBL = G_TYPE_DOUBLE, + FB_JSON_TYPE_INT = G_TYPE_INT64, + FB_JSON_TYPE_STR = G_TYPE_STRING +} FbJsonType; /** - * The #GError codes of JSON parser. - **/ -enum fb_json_error + * FbJsonValues: + * + * Represents a JSON value handler. + */ +struct _FbJsonValues { - FB_JSON_ERROR_PARSER + /*< private >*/ + GObject parent; + FbJsonValuesPrivate *priv; }; +/** + * FbJsonValuesClass: + * + * The base class for all #FbJsonValues's. + */ +struct _FbJsonValuesClass +{ + /*< private >*/ + GObjectClass parent_class; +}; -#define FB_JSON_ERROR fb_json_error_quark() +/** + * fb_json_values_get_type: + * + * Returns: The #GType for an #FbJsonValues. + */ +GType +fb_json_values_get_type(void); -GQuark fb_json_error_quark(void); +/** + * fb_json_error_quark: + * + * Gets the #GQuark of the domain of JSON errors. + * + * Returns: The #GQuark of the domain. + */ +GQuark +fb_json_error_quark(void); -json_value *fb_json_new(const gchar *data, gsize length, GError **err); +/** + * fb_json_bldr_new: + * @type: The starting #JsonNodeType. + * + * Creates a new #JsonBuilder. The starting #JsonNodeType is likely to + * be #JSON_NODE_OBJECT. The returned #JsonBuilder should be freed with + * #g_object_unref() when no longer needed. Optionally, instead of + * freeing, the returned #JsonBuilder can be closed with + * #fb_json_bldr_close(). + * + * Returns: The new #JsonBuilder. + */ +JsonBuilder * +fb_json_bldr_new(JsonNodeType type); -gchar *fb_json_valstr(const json_value *json); +/** + * fb_json_bldr_close: + * @bldr: The #JsonBuilder. + * @type: The ending #JsonNodeType. + * @size: The return local for the size of the returned string. + * + * Closes the #JsonBuilder by returning a string representing the + * #JsonBuilder. The ending #JsonNodeType is likely to be + * #JSON_NODE_OBJECT. This calls #g_object_unref(). The returned + * string should be freed with #g_free() when no longer needed. + * + * Returns: The string representation of the #JsonBuilder. + */ +gchar * +fb_json_bldr_close(JsonBuilder *bldr, JsonNodeType type, gsize *size); -json_value *fb_json_val(const json_value *json, const gchar *name, - json_type type); +/** + * fb_json_bldr_arr_begin: + * @bldr: The #JsonBuilder. + * @name: The member name, or #NULL. + * + * Begins an array member in the #JsonBuilder. + */ +void +fb_json_bldr_arr_begin(JsonBuilder *bldr, const gchar *name); -gboolean fb_json_val_chk(const json_value *json, const gchar *name, - json_type type, json_value **val); +/** + * fb_json_bldr_arr_end: + * @bldr: The #JsonBuilder. + * + * Ends an array member in the #JsonBuilder. + */ +void +fb_json_bldr_arr_end(JsonBuilder *bldr); -json_value *fb_json_array(const json_value *json, const gchar *name); +/** + * fb_json_bldr_obj_begin: + * @bldr: The #JsonBuilder. + * @name: The member name, or #NULL. + * + * Begins an object member in the #JsonBuilder. + */ +void +fb_json_bldr_obj_begin(JsonBuilder *bldr, const gchar *name); -gboolean fb_json_array_chk(const json_value *json, const gchar *name, - json_value **val); +/** + * fb_json_bldr_obj_end: + * @bldr: The #JsonBuilder. + * + * Ends an array member in the #JsonBuilder. + */ +void +fb_json_bldr_obj_end(JsonBuilder *bldr); -gboolean fb_json_bool(const json_value *json, const gchar *name); +/** + * fb_json_bldr_add_bool: + * @bldr: The #JsonBuilder. + * @name: The member name, or #NULL. + * @value: The value. + * + * Adds a boolean memeber to the #JsonBuilder. + */ +void +fb_json_bldr_add_bool(JsonBuilder *bldr, const gchar *name, gboolean value); -gboolean fb_json_bool_chk(const json_value *json, const gchar *name, - gboolean *val); +/** + * fb_json_bldr_add_dbl: + * @bldr: The #JsonBuilder. + * @name: The member name, or #NULL. + * @value: The value. + * + * Adds a floating point memeber to the #JsonBuilder. + */ +void +fb_json_bldr_add_dbl(JsonBuilder *bldr, const gchar *name, gdouble value); -gint64 fb_json_int(const json_value *json, const gchar *name); +/** + * fb_json_bldr_add_int: + * @bldr: The #JsonBuilder. + * @name: The member name, or #NULL. + * @value: The value. + * + * Adds a integer memeber to the #JsonBuilder. + */ +void +fb_json_bldr_add_int(JsonBuilder *bldr, const gchar *name, gint64 value); -gboolean fb_json_int_chk(const json_value *json, const gchar *name, - gint64 *val); +/** + * fb_json_bldr_add_str: + * @bldr: The #JsonBuilder. + * @name: The member name, or #NULL. + * @value: The value. + * + * Adds a string memeber to the #JsonBuilder. + */ +void +fb_json_bldr_add_str(JsonBuilder *bldr, const gchar *name, const gchar *value); -const gchar *fb_json_str(const json_value *json, const gchar *name); +/** + * fb_json_bldr_add_strf: + * @bldr: The #JsonBuilder. + * @name: The member name, or #NULL. + * @format: The format string literal. + * @...: The arguments for @format. + * + * Adds a formatted string memeber to the #JsonBuilder. + */ +void +fb_json_bldr_add_strf(JsonBuilder *bldr, const gchar *name, + const gchar *format, ...) + G_GNUC_PRINTF(3, 4); -gboolean fb_json_str_chk(const json_value *json, const gchar *name, - const gchar **val); +/** + * fb_json_node_new: + * @data: The string JSON. + * @size: The size of @json, or -1 if null-terminated. + * @error: The return location for the #GError, or #NULL. + * + * Creates a new #JsonNode. The returned #JsonBuilder should be freed + * wuth #json_node_free() when no longer needed. + * + * Returns: The new #JsonNode. + */ +JsonNode * +fb_json_node_new(const gchar *data, gssize size, GError **error); -gchar *fb_json_str_escape(const gchar *str); +/** + * fb_json_node_get: + * @root: The root #JsonNode. + * @expr: The #JsonPath expression. + * @error: The return location for the #GError, or #NULL. + * + * Gets a new #JsonNode value from a parent #JsonNode with a #JsonPath + * expression. The returned #JsonNode should be freed with + * #json_node_free() when no longer needed. + * + * Returns: The new #JsonNode. + */ +JsonNode * +fb_json_node_get(JsonNode *root, const gchar *expr, GError **error); + +/** + * fb_json_node_get_nth: + * @root: The root #JsonNode. + * @n: The index number. + * + * Gets a #JsonNode value from a parent #JsonNode by index. The + * returned #JsonNode should not be freed. + * + * Return: The #JsonNode. + */ +JsonNode * +fb_json_node_get_nth(JsonNode *root, guint n); + +/** + * fb_json_node_get_arr: + * @root: The root #JsonNode. + * @expr: The #JsonPath expression. + * @error: The return location for the #GError, or #NULL. + * + * Gets a new #JsonArray value from a parent #JsonNode with a #JsonPath + * expression. The returned #JsonArray should be freed with + * #json_array_unref() when no longer needed. + * + * Returns: The new #JsonArray. + */ +JsonArray * +fb_json_node_get_arr(JsonNode *root, const gchar *expr, GError **error); + +/** + * fb_json_node_get_bool: + * @root: The root #JsonNode. + * @expr: The #JsonPath expression. + * @error: The return location for the #GError, or #NULL. + * + * Gets a boolean value from a parent #JsonNode with a #JsonPath + * expression. + * + * Returns: The boolean value. + */ +gboolean +fb_json_node_get_bool(JsonNode *root, const gchar *expr, GError **error); + +/** + * fb_json_node_get_dbl: + * @root: The root #JsonNode. + * @expr: The #JsonPath expression. + * @error: The return location for the #GError, or #NULL. + * + * Gets a floating point value from a parent #JsonNode with a #JsonPath + * expression. + * + * Returns: The floating point value. + */ +gdouble +fb_json_node_get_dbl(JsonNode *root, const gchar *expr, GError **error); + +/** + * fb_json_node_get_int: + * @root: The root #JsonNode. + * @expr: The #JsonPath expression. + * @error: The return location for the #GError, or #NULL. + * + * Gets an integer value from a parent #JsonNode with a #JsonPath + * expression. + * + * Returns: The integer value. + */ +gint64 +fb_json_node_get_int(JsonNode *root, const gchar *expr, GError **error); + +/** + * fb_json_node_get_str: + * @root: The root #JsonNode. + * @expr: The #JsonPath expression. + * @error: The return location for the #GError, or #NULL. + * + * Gets an string value from a parent #JsonNode with a #JsonPath + * expression. The returned string should be freed with #g_free() + * when no longer needed. + * + * Returns: The string value. + */ +gchar * +fb_json_node_get_str(JsonNode *root, const gchar *expr, GError **error); + +/** + * fb_json_values_new: + * @root: The root #JsonNode. + * + * Creates a new #FbJsonValues. The returned #FbJsonValues should be + * freed with #g_object_unref when no longer needed. + * + * Returns: The new #FbJsonValues. + */ +FbJsonValues * +fb_json_values_new(JsonNode *root); + +/** + * fb_json_values_add: + * @values: The #FbJsonValues. + * @type: The #FbJsonType. + * @required: TRUE if the node is required, otherwise FALSE. + * @expr: The #JsonPath expression. + * + * Adds a new #FbJsonValue to the #FbJsonValues. + */ +void +fb_json_values_add(FbJsonValues *values, FbJsonType type, gboolean required, + const gchar *expr); + +/** + * fb_json_values_get_root: + * @values: The #FbJsonValues. + * + * Gets the current working root #JsonNode. This is either the current + * array #JsonNode, or the root #JsonNode. The returned #JsonNode + * should not be freed. + */ +JsonNode * +fb_json_values_get_root(FbJsonValues *values); + +/** + * fb_json_values_set_array: + * @values: The #FbJsonValues. + * @required: TRUE if the node is required, otherwise FALSE. + * @expr: The #JsonPath expression. + * + * Sets the #JsonPath for an array to base all #FbJsonValue's off. + */ +void +fb_json_values_set_array(FbJsonValues *values, gboolean required, + const gchar *expr); + +/** + * fb_json_values_update: + * @values: The #FbJsonValues. + * @error: The return location for the #GError, or #NULL. + * + * Updates the current working root. This should be called after all of + * the #FbJsonValue's have been added with #fb_json_values_add(). If an + * array was set with #fb_json_values_set_array(), then this should be + * called in a while loop, until #FALSE is returned. + * + * Returns: #TRUE if the values were updated, otherwise #FALSE. + */ +gboolean +fb_json_values_update(FbJsonValues *values, GError **error); + +/** + * fb_json_values_next: + * @values: The #FbJsonValues. + * + * Gets the next #GValue from the #FbJsonValues. Before calling this + * function, #fb_json_values_update() must be called. + * + * Returns: The #GValue. + */ +const GValue * +fb_json_values_next(FbJsonValues *values); + +/** + * fb_json_values_next_bool: + * @values: The #FbJsonValues. + * @defval: The default value. + * + * Gets the next boolean value from the #FbJsonValues. Before calling + * this function, #fb_json_values_update() must be called. + * + * Returns: The boolean value. + */ +gboolean +fb_json_values_next_bool(FbJsonValues *values, gboolean defval); + +/** + * fb_json_values_next_dbl: + * @values: The #FbJsonValues. + * @defval: The default value. + * + * Gets the next floating point value from the #FbJsonValues. Before + * calling this function, #fb_json_values_update() must be called. + * + * Returns: The floating point value. + */ +gdouble +fb_json_values_next_dbl(FbJsonValues *values, gdouble defval); + +/** + * fb_json_values_next_int: + * @values: The #FbJsonValues. + * @defval: The default value. + * + * Gets the next integer value from the #FbJsonValues. Before calling + * this function, #fb_json_values_update() must be called. + * + * Returns: The integer value. + */ +gint64 +fb_json_values_next_int(FbJsonValues *values, gint64 defval); + +/** + * fb_json_values_next_str: + * @values: The #FbJsonValues. + * @defval: The default value. + * + * Gets the next string value from the #FbJsonValues. Before calling + * this function, #fb_json_values_update() must be called. + * + * Returns: The string value. + */ +const gchar * +fb_json_values_next_str(FbJsonValues *values, const gchar *defval); + +/** + * fb_json_values_next_str_dup: + * @values: The #FbJsonValues. + * @defval: The default value. + * + * Gets the next duplicate string value from the #FbJsonValues. Before + * calling this function, #fb_json_values_update() must be called. + * + * Returns: The duplicate string value. + */ +gchar * +fb_json_values_next_str_dup(FbJsonValues *values, const gchar *defval); -#endif /* _FACEBOOK_JSON_H */ +#endif /* _FACEBOOK_JSON_H_ */ diff --git a/facebook/facebook-mqtt.c b/facebook/facebook-mqtt.c index 6e85b29..de0fee6 100644 --- a/facebook/facebook-mqtt.c +++ b/facebook/facebook-mqtt.c @@ -21,317 +21,405 @@ #include <stdarg.h> #include <string.h> +#include "facebook-marshal.h" #include "facebook-mqtt.h" +#include "facebook-util.h" -/** - * Gets the error domain for #fb_mqtt. - * - * @return The #GQuark of the error domain. - **/ -GQuark fb_mqtt_error_quark(void) +struct _FbMqttPrivate { - static GQuark q; + gpointer ssl; + gboolean connected; + guint16 mid; - if (G_UNLIKELY(q == 0)) - q = g_quark_from_static_string("fb-mqtt-error-quark"); + GByteArray *rbuf; + GByteArray *wbuf; + gsize remz; - return q; -} + gint tev; + gint rev; + gint wev; +}; -/** - * Creates a new #fb_mqtt. The returned #fb_mqtt should be freed with - * #fb_mqtt_free() when no longer needed. - * - * @param funcs The #fb_mqtt_funcs. - * @param data The user defined data or NULL. - * - * @return The #fb_mqtt or NULL on error. - **/ -fb_mqtt_t *fb_mqtt_new(const fb_mqtt_funcs_t *funcs, gpointer data) +struct _FbMqttMessagePrivate { - fb_mqtt_t *mqtt; + FbMqttMessageType type; + FbMqttMessageFlags flags; - mqtt = g_new0(fb_mqtt_t, 1); - memcpy(&mqtt->funcs, funcs, sizeof *funcs); - mqtt->data = data; - mqtt->rbuf = g_byte_array_new(); - mqtt->wbuf = g_byte_array_new(); + GByteArray *bytes; + guint offset; + guint pos; - return mqtt; + gboolean local; }; -/** - * Frees all memory used by a #fb_mqtt. - * - * @param api The #fb_mqtt. - **/ -void fb_mqtt_free(fb_mqtt_t *mqtt) +G_DEFINE_TYPE(FbMqtt, fb_mqtt, G_TYPE_OBJECT); +G_DEFINE_TYPE(FbMqttMessage, fb_mqtt_message, G_TYPE_OBJECT); + +static void +fb_mqtt_dispose(GObject *obj) { - if (G_UNLIKELY(mqtt == NULL)) - return; + FbMqtt *mqtt = FB_MQTT(obj); + FbMqttPrivate *priv = mqtt->priv; fb_mqtt_close(mqtt); - g_clear_error(&mqtt->err); + g_byte_array_free(priv->rbuf, TRUE); + g_byte_array_free(priv->wbuf, TRUE); +} + +static void +fb_mqtt_class_init(FbMqttClass *klass) +{ + GObjectClass *gklass = G_OBJECT_CLASS(klass); + + gklass->dispose = fb_mqtt_dispose; + g_type_class_add_private(klass, sizeof (FbMqttPrivate)); + + /** + * FbMqtt::connect: + * @mqtt: The #FbMqtt. + * + * Emitted upon the successful completion of the connection + * process. This is emitted as a result of #fb_mqtt_connect(). + */ + g_signal_new("connect", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + /** + * FbMqtt::error: + * @mqtt: The #FbMqtt. + * @error: The #GError. + * + * Emitted whenever an error is hit within the #FbMqtt. This should + * close the #FbMqtt with #fb_mqtt_close(). + */ + g_signal_new("error", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, G_TYPE_ERROR); + + /** + * FbMqtt::open: + * @mqtt: The #FbMqtt. + * + * Emitted upon the successful opening of the remote socket. + * This is emitted as a result of #fb_mqtt_open(). This should + * call #fb_mqtt_connect(). + */ + g_signal_new("open", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + /** + * FbMqtt::publish: + * @mqtt: The #FbMqtt. + * @topic: The topic. + * @pload: The payload. + * + * Emitted upon an incoming message from the steam. + */ + g_signal_new("publish", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__STRING_BOXED, + G_TYPE_NONE, + 2, G_TYPE_STRING, G_TYPE_BYTE_ARRAY); +} + +static void +fb_mqtt_init(FbMqtt *mqtt) +{ + FbMqttPrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(mqtt, FB_TYPE_MQTT, FbMqttPrivate); + mqtt->priv = priv; + + priv->rbuf = g_byte_array_new(); + priv->wbuf = g_byte_array_new(); +} + +static void +fb_mqtt_message_dispose(GObject *obj) +{ + FbMqttMessagePrivate *priv = FB_MQTT_MESSAGE(obj)->priv; + + if ((priv->bytes != NULL) && priv->local) { + g_byte_array_free(priv->bytes, TRUE); + } +} - g_byte_array_free(mqtt->wbuf, TRUE); - g_byte_array_free(mqtt->rbuf, TRUE); +static void +fb_mqtt_message_class_init(FbMqttMessageClass *klass) +{ + GObjectClass *gklass = G_OBJECT_CLASS(klass); - g_free(mqtt); + gklass->dispose = fb_mqtt_message_dispose; + g_type_class_add_private(klass, sizeof (FbMqttMessagePrivate)); } -/** - * Closes the #fb_mqtt connection. - * - * @param mqtt The #fb_mqtt. - **/ -void fb_mqtt_close(fb_mqtt_t *mqtt) +static void +fb_mqtt_message_init(FbMqttMessage *msg) +{ + FbMqttMessagePrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(msg, FB_TYPE_MQTT_MESSAGE, + FbMqttMessagePrivate); + msg->priv = priv; +} + +GQuark +fb_mqtt_error_quark(void) { - g_return_if_fail(mqtt != NULL); + static GQuark q = 0; - if (mqtt->wev > 0) { - b_event_remove(mqtt->wev); - mqtt->wev = 0; + if (G_UNLIKELY(q == 0)) { + q = g_quark_from_static_string("fb-mqtt-error-quark"); } - if (mqtt->rev > 0) { - b_event_remove(mqtt->rev); - mqtt->rev = 0; + return q; +} + +GQuark +fb_mqtt_ssl_error_quark(void) +{ + static GQuark q = 0; + + if (G_UNLIKELY(q == 0)) { + q = g_quark_from_static_string("fb-mqtt-ssl-error-quark"); } - if (mqtt->tev > 0) { - b_event_remove(mqtt->tev); - mqtt->tev = 0; + return q; +} + +FbMqtt * +fb_mqtt_new(void) +{ + return g_object_new(FB_TYPE_MQTT, NULL); +}; + +void +fb_mqtt_close(FbMqtt *mqtt) +{ + FbMqttPrivate *priv; + + g_return_if_fail(FB_IS_MQTT(mqtt)); + priv = mqtt->priv; + + if (priv->wev > 0) { + b_event_remove(priv->wev); + priv->wev = 0; } - if (mqtt->ssl != NULL) { - ssl_disconnect(mqtt->ssl); - mqtt->ssl = NULL; + if (priv->rev > 0) { + b_event_remove(priv->rev); + priv->rev = 0; } -#ifdef DEBUG_FACEBOOK - if (mqtt->wbuf->len > 0) - FB_UTIL_DEBUGLN("Closing with unwritten data"); -#endif /* DEBUG_FACEBOOK */ + if (priv->tev > 0) { + b_event_remove(priv->tev); + priv->tev = 0; + } + + if (priv->ssl != NULL) { + ssl_disconnect(priv->ssl); + priv->ssl = NULL; + } - mqtt->connected = FALSE; - g_clear_error(&mqtt->err); + if (priv->wbuf->len > 0) { + fb_util_debug_warn("Closing with unwritten data"); + } - g_byte_array_set_size(mqtt->rbuf, 0); - g_byte_array_set_size(mqtt->wbuf, 0); + priv->connected = FALSE; + g_byte_array_set_size(priv->rbuf, 0); + g_byte_array_set_size(priv->wbuf, 0); } -/** - * Handles an error with the #fb_mqtt. This sets #fb_mqtt->err, calls - * the error function, and closes the connection. - * - * @param mqtt The #fb_mqtt. - * @param error The #fb_mqtt_error. - * @param fmt The format string. - * @param ... The arguments for the format string. - **/ -void fb_mqtt_error(fb_mqtt_t *mqtt, fb_mqtt_error_t err, const gchar *fmt, ...) +void +fb_mqtt_error(FbMqtt *mqtt, FbMqttError error, const gchar *format, ...) { - gchar *str; - va_list ap; - - g_return_if_fail(mqtt != NULL); + GError *err; + va_list ap; - if (fmt != NULL) { - va_start(ap, fmt); - str = g_strdup_vprintf(fmt, ap); - va_end(ap); + g_return_if_fail(FB_IS_MQTT(mqtt)); - g_clear_error(&mqtt->err); - g_set_error_literal(&mqtt->err, FB_MQTT_ERROR, err, str); - g_free(str); - } + va_start(ap, format); + err = g_error_new_valist(FB_MQTT_ERROR, error, format, ap); + va_end(ap); - if (mqtt->err != NULL) - FB_MQTT_FUNC(mqtt, error, mqtt->err); + g_signal_emit_by_name(mqtt, "error", err); + g_error_free(err); } -/** - * Implemented #b_event_handler for #fb_mqtt_timeout(). - * - * @param data The user defined data, which is #fb_mqtt. - * @param fd The event file descriptor. - * @param cond The #b_input_condition. - * - * @return FALSE to prevent continued event handling. - **/ -static gboolean fb_mqtt_cb_timeout(gpointer data, gint fd, - b_input_condition cond) +static gboolean +fb_mqtt_cb_timeout(gpointer data, gint fd, b_input_condition cond) { - fb_mqtt_t *mqtt = data; + FbMqtt *mqtt = data; + FbMqttPrivate *priv = mqtt->priv; - mqtt->tev = 0; + priv->tev = 0; fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, "Connection timed out"); return FALSE; } -/** - * Clears an enacted connection timeout. - * - * @param mqtt The #fb_mqtt. - **/ -static void fb_mqtt_timeout_clear(fb_mqtt_t *mqtt) +static void +fb_mqtt_timeout_clear(FbMqtt *mqtt) { - g_return_if_fail(mqtt != NULL); + FbMqttPrivate *priv = mqtt->priv; - if (mqtt->tev > 0) { - b_event_remove(mqtt->tev); - mqtt->tev = 0; + if (priv->tev > 0) { + b_event_remove(priv->tev); + priv->tev = 0; } } -/** - * Enacts a timeout on the connection. This clears any timeout which - * currently exists. - * - * @param mqtt The #fb_mqtt. - **/ -static void fb_mqtt_timeout(fb_mqtt_t *mqtt) +static void +fb_mqtt_timeout(FbMqtt *mqtt) { - g_return_if_fail(mqtt != NULL); + FbMqttPrivate *priv = mqtt->priv; fb_mqtt_timeout_clear(mqtt); - mqtt->tev = b_timeout_add(FB_MQTT_TIMEOUT_CONN, fb_mqtt_cb_timeout, mqtt); + priv->tev = b_timeout_add(FB_MQTT_TIMEOUT_CONN, fb_mqtt_cb_timeout, mqtt); } -/** - * Implemented #b_event_handler for sending a PING request. - * - * @param data The user defined data, which is #fb_mqtt. - * @param fd The event file descriptor. - * @param cond The #b_input_condition. - * - * @return FALSE to prevent continued event handling. - **/ -static gboolean fb_mqtt_cb_ping(gpointer data, gint fd, - b_input_condition cond) +static gboolean +fb_mqtt_cb_ping(gpointer data, gint fd, b_input_condition cond) { - fb_mqtt_t *mqtt = data; - fb_mqtt_msg_t *msg; + FbMqtt *mqtt = data; + FbMqttMessage *msg; + FbMqttPrivate *priv = mqtt->priv; - msg = fb_mqtt_msg_new(FB_MQTT_MSG_TYPE_PINGREQ, 0); + msg = fb_mqtt_message_new(FB_MQTT_MESSAGE_TYPE_PINGREQ, 0); fb_mqtt_write(mqtt, msg); - fb_mqtt_msg_free(msg); + g_object_unref(msg); - mqtt->tev = 0; + priv->tev = 0; fb_mqtt_timeout(mqtt); return FALSE; } -/** - * Sends a PING after #FB_MQTT_KA seconds. This clears any timeout which - * currently exists. - * - * @param mqtt The #fb_mqtt. - **/ -static void fb_mqtt_ping(fb_mqtt_t *mqtt) +static void +fb_mqtt_ping(FbMqtt *mqtt) { - g_return_if_fail(mqtt != NULL); + FbMqttPrivate *priv = mqtt->priv; fb_mqtt_timeout_clear(mqtt); - mqtt->tev = b_timeout_add(FB_MQTT_TIMEOUT_PING, fb_mqtt_cb_ping, mqtt); + priv->tev = b_timeout_add(FB_MQTT_TIMEOUT_PING, fb_mqtt_cb_ping, mqtt); } -/** - * Implemented #b_event_handler for the read of #fb_mqtt->fd. - * - * @param data The user defined data, which is #fb_mqtt. - * @param fd The event file descriptor. - * @param cond The #b_input_condition. - * - * @return TRUE for continued event handling, otherwise FALSE. - **/ -static gboolean fb_mqtt_cb_read(gpointer data, gint fd, - b_input_condition cond) +static gboolean +fb_mqtt_cb_read(gpointer data, gint fd, b_input_condition cond) { - fb_mqtt_t *mqtt = data; - fb_mqtt_msg_t *msg; - gchar buf[1024]; - guint8 byte; - guint mult; - gssize rize; - gint res; - - if (mqtt->remz < 1) { + FbMqtt *mqtt = data; + FbMqttMessage *msg; + FbMqttPrivate *priv = mqtt->priv; + gint res; + guint mult; + guint8 buf[1024]; + guint8 byte; + gsize size; + gssize rize; + + if (priv->remz < 1) { /* Reset the read buffer */ - g_byte_array_set_size(mqtt->rbuf, 0); + g_byte_array_set_size(priv->rbuf, 0); - res = ssl_read(mqtt->ssl, (gchar*) &byte, sizeof byte); - g_byte_array_append(mqtt->rbuf, &byte, sizeof byte); + res = ssl_read(priv->ssl, (gchar *) &byte, sizeof byte); + g_byte_array_append(priv->rbuf, &byte, sizeof byte); - if (res != sizeof byte) - goto error; + if (res != sizeof byte) { + fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, + "Failed to read fixed header"); + return FALSE; + } mult = 1; do { - res = ssl_read(mqtt->ssl, (gchar*) &byte, sizeof byte); - g_byte_array_append(mqtt->rbuf, &byte, sizeof byte); + res = ssl_read(priv->ssl, (gchar *) &byte, sizeof byte); + g_byte_array_append(priv->rbuf, &byte, sizeof byte); - if (res != sizeof byte) - goto error; + if (res != sizeof byte) { + fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, + "Failed to read packet size"); + return FALSE; + } - mqtt->remz += (byte & 127) * mult; + priv->remz += (byte & 127) * mult; mult *= 128; } while ((byte & 128) != 0); } - if (mqtt->remz > 0) { - rize = ssl_read(mqtt->ssl, buf, MIN(mqtt->remz, sizeof buf)); + if (priv->remz > 0) { + size = MIN(priv->remz, sizeof buf); + rize = ssl_read(priv->ssl, (gchar *) buf, size); - if (rize < 1) - goto error; + if (rize < 1) { + fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, + "Failed to read packet data"); + return FALSE; + } - g_byte_array_append(mqtt->rbuf, (guint8*) buf, rize); - mqtt->remz -= rize; + g_byte_array_append(priv->rbuf, buf, rize); + priv->remz -= rize; } - if (mqtt->remz < 1) { - msg = fb_mqtt_msg_new_bytes(mqtt->rbuf); - mqtt->remz = 0; + if (priv->remz < 1) { + msg = fb_mqtt_message_new_bytes(priv->rbuf); + priv->remz = 0; - if (G_UNLIKELY(msg == NULL)) - goto error; + if (G_UNLIKELY(msg == NULL)) { + fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, + "Failed to parse message"); + return FALSE; + } fb_mqtt_read(mqtt, msg); - fb_mqtt_msg_free(msg); + g_object_unref(msg); } return TRUE; - -error: - fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, "Short read"); - return FALSE; } -/** - * Read a #GByteArray to the #fb_mqtt. - * - * @param mqtt The #fb_mqtt. - * @param bytes The #GByteArray. - **/ -void fb_mqtt_read(fb_mqtt_t *mqtt, fb_mqtt_msg_t *msg) +void +fb_mqtt_read(FbMqtt *mqtt, FbMqttMessage *msg) { - fb_mqtt_msg_t *nsg; - GByteArray *wytes; - gchar *str; - guint8 chr; - guint16 mid; - - g_return_if_fail(mqtt != NULL); - g_return_if_fail(msg != NULL); - - fb_util_hexdump(msg->bytes, 2, "Reading %d (flags: 0x%0X)", - msg->type, msg->flags); - - switch (msg->type) { - case FB_MQTT_MSG_TYPE_CONNACK: - if (!fb_mqtt_msg_read_byte(msg, NULL) || - !fb_mqtt_msg_read_byte(msg, &chr)) + FbMqttMessage *nsg; + FbMqttPrivate *priv; + FbMqttMessagePrivate *mriv; + GByteArray *wytes; + gchar *str; + guint8 chr; + guint16 mid; + + g_return_if_fail(FB_IS_MQTT(mqtt)); + g_return_if_fail(FB_IS_MQTT_MESSAGE(msg)); + priv = mqtt->priv; + mriv = msg->priv; + + fb_util_debug_hexdump(FB_UTIL_DEBUG_LEVEL_INFO, mriv->bytes, + "Reading %d (flags: 0x%0X)", + mriv->type, mriv->flags); + + switch (mriv->type) { + case FB_MQTT_MESSAGE_TYPE_CONNACK: + if (!fb_mqtt_message_read_byte(msg, NULL) || + !fb_mqtt_message_read_byte(msg, &chr)) { break; } @@ -341,62 +429,67 @@ void fb_mqtt_read(fb_mqtt_t *mqtt, fb_mqtt_msg_t *msg) return; } - mqtt->connected = TRUE; + priv->connected = TRUE; fb_mqtt_ping(mqtt); - FB_MQTT_FUNC(mqtt, connack); + g_signal_emit_by_name(mqtt, "connect"); return; - case FB_MQTT_MSG_TYPE_PUBLISH: - if (!fb_mqtt_msg_read_str(msg, &str)) + case FB_MQTT_MESSAGE_TYPE_PUBLISH: + if (!fb_mqtt_message_read_str(msg, &str)) { break; + } - if ((msg->flags & FB_MQTT_MSG_FLAG_QOS1) || - (msg->flags & FB_MQTT_MSG_FLAG_QOS2)) + if ((mriv->flags & FB_MQTT_MESSAGE_FLAG_QOS1) || + (mriv->flags & FB_MQTT_MESSAGE_FLAG_QOS2)) { - if (msg->flags & FB_MQTT_MSG_FLAG_QOS1) - chr = FB_MQTT_MSG_TYPE_PUBACK; - else - chr = FB_MQTT_MSG_TYPE_PUBREC; + if (mriv->flags & FB_MQTT_MESSAGE_FLAG_QOS1) { + chr = FB_MQTT_MESSAGE_TYPE_PUBACK; + } else { + chr = FB_MQTT_MESSAGE_TYPE_PUBREC; + } - if (!fb_mqtt_msg_read_mid(msg, &mid)) + if (!fb_mqtt_message_read_mid(msg, &mid)) { break; + } - nsg = fb_mqtt_msg_new(chr, 0); - fb_mqtt_msg_write_u16(nsg, mid); + nsg = fb_mqtt_message_new(chr, 0); + fb_mqtt_message_write_u16(nsg, mid); fb_mqtt_write(mqtt, nsg); - fb_mqtt_msg_free(nsg); + g_object_unref(nsg); } wytes = g_byte_array_new(); - fb_mqtt_msg_read_r(msg, wytes); - FB_MQTT_FUNC(mqtt, publish, str, wytes); + fb_mqtt_message_read_r(msg, wytes); + g_signal_emit_by_name(mqtt, "publish", str, wytes); g_byte_array_free(wytes, TRUE); g_free(str); return; - case FB_MQTT_MSG_TYPE_PUBREL: - if (!fb_mqtt_msg_read_mid(msg, &mid)) + case FB_MQTT_MESSAGE_TYPE_PUBREL: + if (!fb_mqtt_message_read_mid(msg, &mid)) { break; + } - nsg = fb_mqtt_msg_new(FB_MQTT_MSG_TYPE_PUBCOMP, 0); - fb_mqtt_msg_write_u16(nsg, mid); /* Message identifier */ + nsg = fb_mqtt_message_new(FB_MQTT_MESSAGE_TYPE_PUBCOMP, 0); + fb_mqtt_message_write_u16(nsg, mid); /* Message identifier */ fb_mqtt_write(mqtt, nsg); - fb_mqtt_msg_free(nsg); + g_object_unref(nsg); return; - case FB_MQTT_MSG_TYPE_PINGRESP: + case FB_MQTT_MESSAGE_TYPE_PINGRESP: fb_mqtt_ping(mqtt); return; - case FB_MQTT_MSG_TYPE_PUBACK: - case FB_MQTT_MSG_TYPE_PUBCOMP: - case FB_MQTT_MSG_TYPE_SUBACK: - case FB_MQTT_MSG_TYPE_UNSUBACK: + case FB_MQTT_MESSAGE_TYPE_PUBACK: + case FB_MQTT_MESSAGE_TYPE_PUBCOMP: + case FB_MQTT_MESSAGE_TYPE_SUBACK: + case FB_MQTT_MESSAGE_TYPE_UNSUBACK: return; default: - fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, "Unknown packet (%u)", - msg->type); + fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, + "Unknown packet (%u)", + mriv->type); return; } @@ -404,654 +497,482 @@ void fb_mqtt_read(fb_mqtt_t *mqtt, fb_mqtt_msg_t *msg) fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, "Failed to parse message"); } -/** - * Implemented #b_event_handler for the writing of #fb_mqtt->fd. - * - * @param data The user defined data, which is #fb_mqtt. - * @param fd The event file descriptor. - * @param cond The #b_input_condition. - * - * @return TRUE for continued event handling, otherwise FALSE. - **/ -static gboolean fb_mqtt_cb_write(gpointer data, gint fd, - b_input_condition cond) +static gboolean +fb_mqtt_cb_write(gpointer data, gint fd, b_input_condition cond) { - fb_mqtt_t *mqtt = data; - gssize wize; + FbMqtt *mqtt = data; + FbMqttPrivate *priv = mqtt->priv; + gssize wize; - wize = ssl_write(mqtt->ssl, (gchar*) mqtt->wbuf->data, mqtt->wbuf->len); + wize = ssl_write(priv->ssl, (gchar *) priv->wbuf->data, priv->wbuf->len); if (wize < 0) { fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, "Failed to write data"); return FALSE; } - if (wize > 0) - g_byte_array_remove_range(mqtt->wbuf, 0, wize); + if (wize > 0) { + g_byte_array_remove_range(priv->wbuf, 0, wize); + } - if (mqtt->wbuf->len < 1) { - mqtt->wev = 0; + if (priv->wbuf->len < 1) { + priv->wev = 0; return FALSE; } return TRUE; } -/** - * Writes a #fb_mqtt_msg to the #fb_mqtt. - * - * @param mqtt The #fb_mqtt. - * @param msg The #fb_mqtt_msg. - **/ -void fb_mqtt_write(fb_mqtt_t *mqtt, fb_mqtt_msg_t *msg) +void +fb_mqtt_write(FbMqtt *mqtt, FbMqttMessage *msg) { const GByteArray *bytes; + FbMqttMessagePrivate *mriv; + FbMqttPrivate *priv; gint fd; - g_return_if_fail(mqtt != NULL); + g_return_if_fail(FB_IS_MQTT(mqtt)); + g_return_if_fail(FB_IS_MQTT_MESSAGE(msg)); + priv = mqtt->priv; + mriv = msg->priv; - bytes = fb_mqtt_msg_bytes(msg); + bytes = fb_mqtt_message_bytes(msg); if (G_UNLIKELY(bytes == NULL)) { fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, "Failed to format data"); return; } - fb_util_hexdump(bytes, 2, "Writing %d (flags: 0x%0X)", - msg->type, msg->flags); + fb_util_debug_hexdump(FB_UTIL_DEBUG_LEVEL_INFO, mriv->bytes, + "Writing %d (flags: 0x%0X)", + mriv->type, mriv->flags); - fd = ssl_getfd(mqtt->ssl); - g_byte_array_append(mqtt->wbuf, bytes->data, bytes->len); + fd = ssl_getfd(priv->ssl); + g_byte_array_append(priv->wbuf, bytes->data, bytes->len); + fb_mqtt_cb_write(mqtt, fd, B_EV_IO_WRITE); - if ((mqtt->wev < 1) && fb_mqtt_cb_write(mqtt, fd, B_EV_IO_WRITE)) - mqtt->wev = b_input_add(fd, B_EV_IO_WRITE, fb_mqtt_cb_write, mqtt); + if (priv->wev > 0) { + priv->wev = b_input_add(fd, B_EV_IO_WRITE, fb_mqtt_cb_write, mqtt); + } } -/** - * Implemented #ssl_input_function for the connection of #fb_mqtt->ssl. - * - * @param data The user defined data, which is #fb_mqtt. - * @param error The SSL error. (0 on success) - * @param ssl The SSL source. - * @param cond The #b_input_condition. - * - * @return TRUE for continued event handling, otherwise FALSE. - **/ -static gboolean fb_mqtt_cb_open(gpointer data, gint error, gpointer ssl, - b_input_condition cond) +static gboolean +fb_mqtt_cb_open(gpointer data, gint error, gpointer ssl, + b_input_condition cond) { - fb_mqtt_t *mqtt = data; - gint fd; + FbMqtt *mqtt = data; + FbMqttPrivate *priv = mqtt->priv; + gint fd; if ((ssl == NULL) || (error != SSL_OK)) { fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, "Failed to connect"); return FALSE; } + fd = ssl_getfd(priv->ssl); fb_mqtt_timeout_clear(mqtt); - fd = ssl_getfd(mqtt->ssl); - mqtt->rev = b_input_add(fd, B_EV_IO_READ, fb_mqtt_cb_read, mqtt); - - FB_MQTT_FUNC(mqtt, open); + priv->rev = b_input_add(fd, B_EV_IO_READ, fb_mqtt_cb_read, mqtt); + g_signal_emit_by_name(mqtt, "open"); return FALSE; } -/** - * Opens the connection to the MQTT service. - * - * @param mqtt The #fb_mqtt. - **/ -void fb_mqtt_open(fb_mqtt_t *mqtt, const gchar *host, gint port) +void +fb_mqtt_open(FbMqtt *mqtt, const gchar *host, gint port) { - g_return_if_fail(mqtt != NULL); + FbMqttPrivate *priv; + + g_return_if_fail(FB_IS_MQTT(mqtt)); + priv = mqtt->priv; fb_mqtt_close(mqtt); - mqtt->ssl = ssl_connect((gchar*) host, port, TRUE, fb_mqtt_cb_open, mqtt); + priv->ssl = ssl_connect((gchar *) host, port, TRUE, fb_mqtt_cb_open, mqtt); - if (mqtt->ssl == NULL) { - fb_mqtt_cb_open(mqtt, 1, NULL, 0); + if (priv->ssl == NULL) { + fb_mqtt_cb_open(mqtt, SSL_NOHANDSHAKE, NULL, 0); return; } fb_mqtt_timeout(mqtt); } -/** - * Connects to the MQTT service. This first establishes an SSL based - * socket. Then it sends the initial connection packet with optional - * arguments, which correspond to the flags provided. The arguments - * must be passed in order: client identifier, will topic, will - * message, username, and password (not required). The arguments must - * be in a string format. - * - * @param mqtt The #fb_mqtt. - * @param timeout The keep-alive timeout (seconds). - * @param flags The #fb_mqtt_connect_flags. - * @param cid The client identifier. - * @param ... Additional arguments in order, NULL-terminated. - **/ -void fb_mqtt_connect(fb_mqtt_t *mqtt, guint8 flags, const gchar *cid, ...) +void +fb_mqtt_connect(FbMqtt *mqtt, guint8 flags, const GByteArray *pload) { - fb_mqtt_msg_t *msg; - va_list ap; - const gchar *str; - - g_return_if_fail(mqtt != NULL); + FbMqttMessage *msg; - if (G_UNLIKELY(fb_mqtt_connected(mqtt, FALSE))) - return; + g_return_if_fail(!fb_mqtt_connected(mqtt, FALSE)); + g_return_if_fail(pload != NULL); /* Facebook always sends a CONNACK, use QoS1 */ flags |= FB_MQTT_CONNECT_FLAG_QOS1; - msg = fb_mqtt_msg_new(FB_MQTT_MSG_TYPE_CONNECT, 0); - fb_mqtt_msg_write_str(msg, FB_MQTT_NAME); /* Protocol name */ - fb_mqtt_msg_write_byte(msg, FB_MQTT_VERS); /* Protocol version */ - fb_mqtt_msg_write_byte(msg, flags); /* Flags */ - fb_mqtt_msg_write_u16(msg, FB_MQTT_KA); /* Keep alive */ - fb_mqtt_msg_write_str(msg, cid); /* Client identifier */ - - va_start(ap, cid); - - while ((str = va_arg(ap, const gchar*)) != NULL) - fb_mqtt_msg_write_str(msg, str); - - va_end(ap); + msg = fb_mqtt_message_new(FB_MQTT_MESSAGE_TYPE_CONNECT, 0); + fb_mqtt_message_write_str(msg, FB_MQTT_NAME); /* Protocol name */ + fb_mqtt_message_write_byte(msg, FB_MQTT_LEVEL); /* Protocol level */ + fb_mqtt_message_write_byte(msg, flags); /* Flags */ + fb_mqtt_message_write_u16(msg, FB_MQTT_KA); /* Keep alive */ + fb_mqtt_message_write(msg, pload->data, pload->len); fb_mqtt_write(mqtt, msg); - fb_mqtt_msg_free(msg); + fb_mqtt_timeout(mqtt); + g_object_unref(msg); } -/** - * Checks the #fb_mqtt connection. - * - * @param mqtt The #fb_mqtt. - * @param error TRUE to error upon no connection, FALSE otherwise. - * - * @return TRUE if the #fb_mqtt is connected, FALSE otherwise. - **/ -gboolean fb_mqtt_connected(fb_mqtt_t *mqtt, gboolean error) +gboolean +fb_mqtt_connected(FbMqtt *mqtt, gboolean error) { + FbMqttPrivate *priv; gboolean connected; - g_return_val_if_fail(mqtt != NULL, FALSE); + g_return_val_if_fail(FB_IS_MQTT(mqtt), FALSE); + priv = mqtt->priv; + connected = (priv->ssl != NULL) && priv->connected; - connected = (mqtt->ssl != NULL) && mqtt->connected; - - if (!connected && error) + if (!connected && error) { fb_mqtt_error(mqtt, FB_MQTT_ERROR_GENERAL, "Not connected"); + } return connected; } -/** - * Disconnects from the MQTT service. This cleanly disconnects from the - * MQTT services, rather than killing the socket stream. This closes - * the #fb_mqtt via #fb_mqtt_close(). - * - * @param mqtt The #fb_mqtt. - **/ -void fb_mqtt_disconnect(fb_mqtt_t *mqtt) +void +fb_mqtt_disconnect(FbMqtt *mqtt) { - fb_mqtt_msg_t *msg; + FbMqttMessage *msg; - g_return_if_fail(mqtt != NULL); - - if (G_UNLIKELY(!fb_mqtt_connected(mqtt, FALSE))) + if (G_UNLIKELY(!fb_mqtt_connected(mqtt, FALSE))) { return; + } - msg = fb_mqtt_msg_new(FB_MQTT_MSG_TYPE_DISCONNECT, 0); + msg = fb_mqtt_message_new(FB_MQTT_MESSAGE_TYPE_DISCONNECT, 0); fb_mqtt_write(mqtt, msg); - fb_mqtt_msg_free(msg); + g_object_unref(msg); fb_mqtt_close(mqtt); } -/** - * Publishes a message to MQTT service. - * - * @param mqtt The #fb_mqtt. - * @param topic The message topic. - * @param pload The #GByteArray payload or NULL. - **/ -void fb_mqtt_publish(fb_mqtt_t *mqtt, const gchar *topic, - const GByteArray *pload) +void +fb_mqtt_publish(FbMqtt *mqtt, const gchar *topic, const GByteArray *pload) { - fb_mqtt_msg_t *msg; - - g_return_if_fail(mqtt != NULL); + FbMqttMessage *msg; + FbMqttPrivate *priv; - if (G_UNLIKELY(!fb_mqtt_connected(mqtt, TRUE))) - return; + g_return_if_fail(FB_IS_MQTT(mqtt)); + g_return_if_fail(fb_mqtt_connected(mqtt, FALSE)); + priv = mqtt->priv; /* Message identifier not required, but for consistency use QoS1 */ - msg = fb_mqtt_msg_new(FB_MQTT_MSG_TYPE_PUBLISH, FB_MQTT_MSG_FLAG_QOS1); + msg = fb_mqtt_message_new(FB_MQTT_MESSAGE_TYPE_PUBLISH, + FB_MQTT_MESSAGE_FLAG_QOS1); - fb_mqtt_msg_write_str(msg, topic); /* Message topic */ - fb_mqtt_msg_write_mid(msg, &mqtt->mid); /* Message identifier */ + fb_mqtt_message_write_str(msg, topic); /* Message topic */ + fb_mqtt_message_write_mid(msg, &priv->mid); /* Message identifier */ - if (pload != NULL) - fb_mqtt_msg_write(msg, pload->data, pload->len); + if (pload != NULL) { + fb_mqtt_message_write(msg, pload->data, pload->len); + } fb_mqtt_write(mqtt, msg); - fb_mqtt_msg_free(msg); + g_object_unref(msg); } -/** - * Subscribes to one or more topics. - * - * @param mqtt The #fb_mqtt. - * @param topic1 The first topic name. - * @param qos1 The first QoS value. - * @param ... Additional topic names and QoS values, NULL-terminated. - **/ -void fb_mqtt_subscribe(fb_mqtt_t *mqtt, const gchar *topic1, guint16 qos1, ...) +void +fb_mqtt_subscribe(FbMqtt *mqtt, const gchar *topic1, guint16 qos1, ...) { - fb_mqtt_msg_t *msg; - va_list ap; - const gchar *topic; - guint16 qos; - - g_return_if_fail(mqtt != NULL); + const gchar *topic; + FbMqttMessage *msg; + FbMqttPrivate *priv; + guint16 qos; + va_list ap; - if (G_UNLIKELY(!fb_mqtt_connected(mqtt, TRUE))) - return; + g_return_if_fail(FB_IS_MQTT(mqtt)); + g_return_if_fail(fb_mqtt_connected(mqtt, FALSE)); + priv = mqtt->priv; /* Facebook requires a message identifier, use QoS1 */ - msg = fb_mqtt_msg_new(FB_MQTT_MSG_TYPE_SUBSCRIBE, FB_MQTT_MSG_FLAG_QOS1); + msg = fb_mqtt_message_new(FB_MQTT_MESSAGE_TYPE_SUBSCRIBE, + FB_MQTT_MESSAGE_FLAG_QOS1); - fb_mqtt_msg_write_mid(msg, &mqtt->mid); /* Message identifier */ - fb_mqtt_msg_write_str(msg, topic1); /* First topics */ - fb_mqtt_msg_write_byte(msg, qos1); /* First QoS value */ + fb_mqtt_message_write_mid(msg, &priv->mid); /* Message identifier */ + fb_mqtt_message_write_str(msg, topic1); /* First topics */ + fb_mqtt_message_write_byte(msg, qos1); /* First QoS value */ va_start(ap, qos1); while ((topic = va_arg(ap, const gchar*)) != NULL) { qos = va_arg(ap, guint); - fb_mqtt_msg_write_str(msg, topic); /* Remaining topics */ - fb_mqtt_msg_write_byte(msg, qos); /* Remaining QoS values */ + fb_mqtt_message_write_str(msg, topic); /* Remaining topics */ + fb_mqtt_message_write_byte(msg, qos); /* Remaining QoS values */ } va_end(ap); fb_mqtt_write(mqtt, msg); - fb_mqtt_msg_free(msg); + g_object_unref(msg); } -/** - * Unsubscribes from one or more topics. - * - * @param mqtt The #fb_mqtt. - * @param topic1 The first topic name. - * @param ... Additional topic names, NULL-terminated. - **/ -void fb_mqtt_unsubscribe(fb_mqtt_t *mqtt, const gchar *topic1, ...) +void +fb_mqtt_unsubscribe(FbMqtt *mqtt, const gchar *topic1, ...) { - fb_mqtt_msg_t *msg; - va_list ap; - const gchar *topic; + const gchar *topic; + FbMqttMessage *msg; + FbMqttPrivate *priv; + va_list ap; - g_return_if_fail(mqtt != NULL); - - if (G_UNLIKELY(!fb_mqtt_connected(mqtt, TRUE))) - return; + g_return_if_fail(FB_IS_MQTT(mqtt)); + g_return_if_fail(fb_mqtt_connected(mqtt, FALSE)); + priv = mqtt->priv; /* Facebook requires a message identifier, use QoS1 */ - msg = fb_mqtt_msg_new(FB_MQTT_MSG_TYPE_UNSUBSCRIBE, FB_MQTT_MSG_FLAG_QOS1); + msg = fb_mqtt_message_new(FB_MQTT_MESSAGE_TYPE_UNSUBSCRIBE, + FB_MQTT_MESSAGE_FLAG_QOS1); - fb_mqtt_msg_write_mid(msg, &mqtt->mid); /* Message identifier */ - fb_mqtt_msg_write_str(msg, topic1); /* First topic */ + fb_mqtt_message_write_mid(msg, &priv->mid); /* Message identifier */ + fb_mqtt_message_write_str(msg, topic1); /* First topic */ va_start(ap, topic1); - while ((topic = va_arg(ap, const gchar*)) != NULL) - fb_mqtt_msg_write_str(msg, topic); /* Remaining topics */ + while ((topic = va_arg(ap, const gchar*)) != NULL) { + fb_mqtt_message_write_str(msg, topic); /* Remaining topics */ + } va_end(ap); fb_mqtt_write(mqtt, msg); - fb_mqtt_msg_free(msg); + g_object_unref(msg); } -/** - * Creates a new #fb_mqtt_msg. The returned #fb_mqtt_msg should be - * freed with #fb_mqtt_msg_free() when no longer needed. - * - * @param type The #fb_mqtt_msg_type. - * @param flags The #fb_mqtt_msg_flags. - * - * @return The #fb_mqtt_msg or NULL on error. - **/ -fb_mqtt_msg_t *fb_mqtt_msg_new(fb_mqtt_msg_type_t type, - fb_mqtt_msg_flags_t flags) +FbMqttMessage * +fb_mqtt_message_new(FbMqttMessageType type, FbMqttMessageFlags flags) { - fb_mqtt_msg_t *msg; + FbMqttMessage *msg; + FbMqttMessagePrivate *priv; + + msg = g_object_new(FB_TYPE_MQTT_MESSAGE, NULL); + priv = msg->priv; - msg = g_new0(fb_mqtt_msg_t, 1); - msg->type = type; - msg->flags = flags; - msg->bytes = g_byte_array_new(); - msg->local = TRUE; + priv->type = type; + priv->flags = flags; + priv->bytes = g_byte_array_new(); + priv->local = TRUE; return msg; } -/** - * Creates a new #fb_mqtt_msg from a #GByteArray containing a raw data. - * The returned #fb_mqtt_msg should be freed with #fb_mqtt_msg_free() - * when no longer needed. The GByteArray passed to this function MUST - * remain for the lifetime of the #fb_mqtt_msg. - * - * @param bytes The #GByteArray. - * - * @return The #fb_mqtt_msg or NULL on error. - **/ -fb_mqtt_msg_t *fb_mqtt_msg_new_bytes(GByteArray *bytes) +FbMqttMessage * +fb_mqtt_message_new_bytes(GByteArray *bytes) { - fb_mqtt_msg_t *msg; - guint8 *byte; + FbMqttMessage *msg; + FbMqttMessagePrivate *priv; + guint8 *byte; - g_return_val_if_fail(bytes != NULL, NULL); + g_return_val_if_fail(bytes != NULL, NULL); g_return_val_if_fail(bytes->len >= 2, NULL); - msg = g_new0(fb_mqtt_msg_t, 1); - msg->bytes = bytes; - msg->local = FALSE; + msg = g_object_new(FB_TYPE_MQTT_MESSAGE, NULL); + priv = msg->priv; - if (bytes->len > 1) { - msg->type = (*bytes->data & 0xF0) >> 4; - msg->flags = *bytes->data & 0x0F; + priv->bytes = bytes; + priv->local = FALSE; + priv->type = (*bytes->data & 0xF0) >> 4; + priv->flags = *bytes->data & 0x0F; - /* Skip the fixed header */ - for (byte = msg->bytes->data + 1; (*(byte++) & 128) != 0; ); - msg->offset = byte - bytes->data; - msg->pos = msg->offset; - } + /* Skip the fixed header */ + for (byte = priv->bytes->data + 1; (*(byte++) & 128) != 0; ); + priv->offset = byte - bytes->data; + priv->pos = priv->offset; return msg; } -/** - * Frees all memory used by a #fb_mqtt_msg. - * - * @param msg The #fb_mqtt_msg. - **/ -void fb_mqtt_msg_free(fb_mqtt_msg_t *msg) +void +fb_mqtt_message_reset(FbMqttMessage *msg) { - g_return_if_fail(msg != NULL); - - if (msg->local) - g_byte_array_free(msg->bytes, TRUE); - - g_free(msg); -} + FbMqttMessagePrivate *priv; -/** - * Resets a #fb_mqtt_msg. This resets the cursor and removes any sort - * of fixed header. - * - * @param msg The #fb_mqtt_msg. - **/ -void fb_mqtt_msg_reset(fb_mqtt_msg_t *msg) -{ - if (G_UNLIKELY(msg == NULL)) - return; + g_return_if_fail(FB_IS_MQTT_MESSAGE(msg)); + priv = msg->priv; - if (msg->offset > 0) { - g_byte_array_remove_range(msg->bytes, 0, msg->offset); - msg->offset = 0; - msg->pos = 0; + if (priv->offset > 0) { + g_byte_array_remove_range(priv->bytes, 0, priv->offset); + priv->offset = 0; + priv->pos = 0; } } -/** - * Formats the internal #GByteArray of a #fb_mqtt_msg with the required - * fixed header for sending over the wire. This set the cursor position - * to the start of the message data. - * - * @param msg The #fb_mqtt_msg. - * - * @return The internal #GByteArray. - **/ -const GByteArray *fb_mqtt_msg_bytes(fb_mqtt_msg_t *msg) +const GByteArray * +fb_mqtt_message_bytes(FbMqttMessage *msg) { - guint8 sbuf[4]; - guint8 byte; + FbMqttMessagePrivate *priv; + guint i; + guint8 byte; + guint8 sbuf[4]; guint32 size; - guint i; - g_return_val_if_fail(msg != NULL, NULL); + g_return_val_if_fail(FB_IS_MQTT_MESSAGE(msg), NULL); + priv = msg->priv; - size = msg->bytes->len - msg->offset; - i = 0; + i = 0; + size = priv->bytes->len - priv->offset; do { - if (G_UNLIKELY(i >= G_N_ELEMENTS(sbuf))) + if (G_UNLIKELY(i >= G_N_ELEMENTS(sbuf))) { return NULL; + } - byte = size % 128; + byte = size % 128; size /= 128; - if (size > 0) + if (size > 0) { byte |= 128; + } sbuf[i++] = byte; } while (size > 0); - fb_mqtt_msg_reset(msg); - g_byte_array_prepend(msg->bytes, sbuf, i); + fb_mqtt_message_reset(msg); + g_byte_array_prepend(priv->bytes, sbuf, i); - byte = ((msg->type & 0x0F) << 4) | (msg->flags & 0x0F); - g_byte_array_prepend(msg->bytes, &byte, sizeof byte); + byte = ((priv->type & 0x0F) << 4) | (priv->flags & 0x0F); + g_byte_array_prepend(priv->bytes, &byte, sizeof byte); - msg->pos = (i + 1) * (sizeof byte); - return msg->bytes; + priv->pos = (i + 1) * (sizeof byte); + return priv->bytes; } -/** - * Reads raw data from a #fb_mqtt_msg. - * - * @param msg The #fb_mqtt_msg. - * @param data The data buffer or NULL. - * @param size The size of data to read. - * - * @return TRUE if the data was completely read, otherwise FALSE. - **/ -gboolean fb_mqtt_msg_read(fb_mqtt_msg_t *msg, gpointer data, guint size) +gboolean +fb_mqtt_message_read(FbMqttMessage *msg, gpointer data, guint size) { - g_return_val_if_fail(msg != NULL, FALSE); + FbMqttMessagePrivate *priv; + + g_return_val_if_fail(FB_IS_MQTT_MESSAGE(msg), FALSE); + priv = msg->priv; - if ((msg->pos + size) > msg->bytes->len) + if ((priv->pos + size) > priv->bytes->len) { return FALSE; + } - if ((data != NULL) && (size > 0)) - memcpy(data, msg->bytes->data + msg->pos, size); + if ((data != NULL) && (size > 0)) { + memcpy(data, priv->bytes->data + priv->pos, size); + } - msg->pos += size; + priv->pos += size; return TRUE; } -/** - * Reads the remaining bytes from a #fb_mqtt_msg into a #GByteArray. - * - * @param msg The #fb_mqtt_msg. - * @param bytes The #GByteArray. - * - * @return TRUE if the byte string was read, otherwise FALSE. - **/ -gboolean fb_mqtt_msg_read_r(fb_mqtt_msg_t *msg, GByteArray *bytes) +gboolean +fb_mqtt_message_read_r(FbMqttMessage *msg, GByteArray *bytes) { + FbMqttMessagePrivate *priv; guint size; - g_return_val_if_fail(bytes != NULL, FALSE); - - size = msg->bytes->len - msg->pos; + g_return_val_if_fail(FB_IS_MQTT_MESSAGE(msg), FALSE); + priv = msg->priv; + size = priv->bytes->len - priv->pos; - if (G_LIKELY(size > 0)) - g_byte_array_append(bytes, msg->bytes->data + msg->pos, size); + if (G_LIKELY(size > 0)) { + g_byte_array_append(bytes, priv->bytes->data + priv->pos, + size); + } return TRUE; } -/** - * Reads a single byte from a #fb_mqtt_msg. If the return location is - * NULL, only the cursor is advanced. - * - * @param msg The #fb_mqtt_msg. - * @param byte The return location for the byte or NULL. - * - * @return TRUE if the byte string was read, otherwise FALSE. - **/ -gboolean fb_mqtt_msg_read_byte(fb_mqtt_msg_t *msg, guint8 *byte) +gboolean +fb_mqtt_message_read_byte(FbMqttMessage *msg, guint8 *value) { - if (byte != NULL) - *byte = 0; - - return fb_mqtt_msg_read(msg, byte, sizeof *byte); + return fb_mqtt_message_read(msg, value, sizeof *value); } -/** - * Reads a message identifier from a #fb_mqtt_msg. If the return - * location is NULL, only the cursor is advanced. - * - * @param msg The #fb_mqtt_msg. - * @param mid The return location for the message identifier or NULL. - * - * @return TRUE if the message identifier was read, otherwise FALSE. - **/ -gboolean fb_mqtt_msg_read_mid(fb_mqtt_msg_t *msg, guint16 *mid) +gboolean +fb_mqtt_message_read_mid(FbMqttMessage *msg, guint16 *value) { - return fb_mqtt_msg_read_u16(msg, mid); + return fb_mqtt_message_read_u16(msg, value); } -/** - * Reads an unsigned 16-bit integer from a #fb_mqtt_msg. If the return - * location is NULL, only the cursor is advanced. - * - * @param msg The #fb_mqtt_msg. - * @param u16 The return location for the integer or NULL. - * - * @return TRUE if the integer was read, otherwise FALSE. - **/ -gboolean fb_mqtt_msg_read_u16(fb_mqtt_msg_t *msg, guint16 *u16) +gboolean +fb_mqtt_message_read_u16(FbMqttMessage *msg, guint16 *value) { - if (!fb_mqtt_msg_read(msg, u16, sizeof *u16)) { - if (u16 != NULL) - *u16 = 0; - + if (!fb_mqtt_message_read(msg, value, sizeof *value)) { return FALSE; } - if (u16 != NULL) - *u16 = g_ntohs(*u16); + if (value != NULL) { + *value = g_ntohs(*value); + } return TRUE; } -/** - * Reads a string from a #fb_mqtt_msg. If the return location is NULL, - * only the cursor is advanced. The returned string should be freed - * with #g_free() when no longer needed. - * - * @param msg The #fb_mqtt_msg. - * @param str The return location for the string or NULL. - * - * @return TRUE if the string was read, otherwise FALSE. - **/ -gboolean fb_mqtt_msg_read_str(fb_mqtt_msg_t *msg, gchar **str) +gboolean +fb_mqtt_message_read_str(FbMqttMessage *msg, gchar **value) { - guint16 size; - guint8 *data; - - if (str != NULL) - *str = NULL; + guint8 *data; + guint16 size; - if (!fb_mqtt_msg_read_u16(msg, &size)) + if (!fb_mqtt_message_read_u16(msg, &size)) { return FALSE; + } - if (str != NULL) { + if (value != NULL) { data = g_new(guint8, size + 1); data[size] = 0; } else { data = NULL; } - if (!fb_mqtt_msg_read(msg, data, size)) { + if (!fb_mqtt_message_read(msg, data, size)) { g_free(data); return FALSE; } - if (str != NULL) - *str = (gchar*) data; + if (value != NULL) { + *value = (gchar*) data; + } return TRUE; } -/** - * Writes raw data to a #fb_mqtt_msg. - * - * @param msg The #fb_mqtt_msg. - * @param data The data. - * @param size The size of the data. - **/ -void fb_mqtt_msg_write(fb_mqtt_msg_t *msg, gconstpointer data, guint size) +void +fb_mqtt_message_write(FbMqttMessage *msg, gconstpointer data, guint size) { - g_return_if_fail(msg != NULL); + FbMqttMessagePrivate *priv; - g_byte_array_append(msg->bytes, data, size); - msg->pos += size; + g_return_if_fail(FB_IS_MQTT_MESSAGE(msg)); + priv = msg->priv; + + g_byte_array_append(priv->bytes, data, size); + priv->pos += size; } -/** - * Writes a single byte to a #fb_mqtt_msg. - * - * @param msg The #fb_mqtt_msg. - * @param byte The byte. - **/ -void fb_mqtt_msg_write_byte(fb_mqtt_msg_t *msg, guint8 byte) +void +fb_mqtt_message_write_byte(FbMqttMessage *msg, guint8 value) { - fb_mqtt_msg_write(msg, &byte, sizeof byte); + fb_mqtt_message_write(msg, &value, sizeof value); } -/** - * Writes a 16-bit message identifier to a #fb_mqtt_msg. This advances - * the message identifier by one before usage. - * - * @param msg The #fb_mqtt_msg. - * @param mid The return location of the message identifier. - **/ -void fb_mqtt_msg_write_mid(fb_mqtt_msg_t *msg, guint16 *mid) +void +fb_mqtt_message_write_mid(FbMqttMessage *msg, guint16 *value) { - g_return_if_fail(mid != NULL); - - fb_mqtt_msg_write_u16(msg, ++(*mid)); + g_return_if_fail(value != NULL); + fb_mqtt_message_write_u16(msg, ++(*value)); } -/** - * Writes an unsigned 16-bit integer to a #fb_mqtt_msg. - * - * @param msg The #fb_mqtt_msg. - * @param u16 Theinteger. - **/ -void fb_mqtt_msg_write_u16(fb_mqtt_msg_t *msg, guint16 u16) +void +fb_mqtt_message_write_u16(FbMqttMessage *msg, guint16 value) { - u16 = g_htons(u16); - fb_mqtt_msg_write(msg, &u16, sizeof u16); + value = g_htons(value); + fb_mqtt_message_write(msg, &value, sizeof value); } -/** - * Writes a string to a #fb_mqtt_msg. - * - * @param msg The #fb_mqtt_msg. - * @param str The string. - **/ -void fb_mqtt_msg_write_str(fb_mqtt_msg_t *msg, const gchar *str) +void +fb_mqtt_message_write_str(FbMqttMessage *msg, const gchar *value) { gint16 size; - g_return_if_fail(str != NULL); + g_return_if_fail(value != NULL); - size = strlen(str); - fb_mqtt_msg_write_u16(msg, size); - fb_mqtt_msg_write(msg, str, size); + size = strlen(value); + fb_mqtt_message_write_u16(msg, size); + fb_mqtt_message_write(msg, value, size); } diff --git a/facebook/facebook-mqtt.h b/facebook/facebook-mqtt.h index c5c63e6..9118a6f 100644 --- a/facebook/facebook-mqtt.h +++ b/facebook/facebook-mqtt.h @@ -15,276 +15,606 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -/** @file **/ +#ifndef _FACEBOOK_MQTT_H_ +#define _FACEBOOK_MQTT_H_ -#ifndef _FACEBOOK_MQTT_H -#define _FACEBOOK_MQTT_H +/** + * SECTION:mqtt + * @section_id: facebook-mqtt + * @short_description: <filename>facebook-mqtt.h</filename> + * @title: MQTT Connection + * + * The MQTT connection. + */ #include <glib.h> +#include <glib-object.h> #include <string.h> -#include "facebook-util.h" +#define FB_TYPE_MQTT (fb_mqtt_get_type()) +#define FB_MQTT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), FB_TYPE_MQTT, FbMqtt)) +#define FB_MQTT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), FB_TYPE_MQTT, FbMqttClass)) +#define FB_IS_MQTT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), FB_TYPE_MQTT)) +#define FB_IS_MQTT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), FB_TYPE_MQTT)) +#define FB_MQTT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), FB_TYPE_MQTT, FbMqttClass)) -#define FB_MQTT_NAME "MQIsdp" -#define FB_MQTT_VERS 3 -#define FB_MQTT_KA 60 -#define FB_MQTT_HOST "mqtt.facebook.com" -#define FB_MQTT_PORT 443 +#define FB_TYPE_MQTT_MESSAGE (fb_mqtt_message_get_type()) +#define FB_MQTT_MESSAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), FB_TYPE_MQTT_MESSAGE, FbMqttMessage)) +#define FB_MQTT_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), FB_TYPE_MQTT_MESSAGE, FbMqttMessageClass)) +#define FB_IS_MQTT_MESSAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), FB_TYPE_MQTT_MESSAGE)) +#define FB_IS_MQTT_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), FB_TYPE_MQTT_MESSAGE)) +#define FB_MQTT_MESSAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), FB_TYPE_MQTT_MESSAGE, FbMqttMessageClass)) -#define FB_MQTT_TIMEOUT_CONN (FB_MQTT_KA * 1500) -#define FB_MQTT_TIMEOUT_PING (FB_MQTT_KA * 1000) +/** + * FB_MQTT_NAME: + * + * The name of the MQTT version. + */ +#define FB_MQTT_NAME "MQTToT" /** - * Executes one of the #fb_mqtt_funcs. + * FB_MQTT_LEVEL: * - * @param m The #fb_mqtt. - * @param f The function to execute. - * @param ... The operational function arguments. - **/ -#define FB_MQTT_FUNC(m, f, ...) \ - G_STMT_START { \ - if (G_LIKELY(m->funcs.f != NULL)) { \ - m->funcs.f(m, ##__VA_ARGS__, m->data); \ - } \ - } G_STMT_END + * The level of the MQTT version. + */ +#define FB_MQTT_LEVEL 3 +/** + * FB_MQTT_KA: + * + * The keep-alive timeout, in seconds, of the MQTT connection. + */ +#define FB_MQTT_KA 60 -/** The flags of #fb_mqtt CONNECT packets. **/ -typedef enum fb_mqtt_connect_flags fb_mqtt_connect_flags_t; +/** + * FB_MQTT_HOST: + * + * The MQTT host name for Facebook. + */ +#define FB_MQTT_HOST "mqtt.facebook.com" -/** The #GError codes of #fb_mqtt. **/ -typedef enum fb_mqtt_error fb_mqtt_error_t; +/** + * FB_MQTT_PORT: + * + * The MQTT host port for Facebook. + */ +#define FB_MQTT_PORT 443 -/** The flags of #fb_mqtt messages. **/ -typedef enum fb_mqtt_msg_flags fb_mqtt_msg_flags_t; +/** + * FB_MQTT_TIMEOUT_CONN: + * + * The timeout, in milliseconds, to wait for a PING back from the + * server. + */ +#define FB_MQTT_TIMEOUT_CONN (FB_MQTT_KA * 1500) -/** The type of #fb_mqtt messages. **/ -typedef enum fb_mqtt_msg_type fb_mqtt_msg_type_t; +/** + * FB_MQTT_TIMEOUT_PING: + * + * The timeout, in milliseconds, to send a PING to the server. + */ +#define FB_MQTT_TIMEOUT_PING (FB_MQTT_KA * 1000) -/** The main structure for #fb_mqtt callback functions. **/ -typedef struct fb_mqtt_funcs fb_mqtt_funcs_t; +/** + * FB_MQTT_ERROR: + * + * The #GQuark of the domain of MQTT errors. + */ +#define FB_MQTT_ERROR fb_mqtt_error_quark() -/** The structure for interacting with Facebook MQTT. **/ -typedef struct fb_mqtt fb_mqtt_t; +/** + * FB_MQTT_SSL_ERROR: + * + * The #GQuark of the domain of MQTT SSL errors. + */ +#define FB_MQTT_SSL_ERROR fb_mqtt_ssl_error_quark() -/** The structure of a #fb_mqtt message. **/ -typedef struct fb_mqtt_msg fb_mqtt_msg_t; +typedef struct _FbMqtt FbMqtt; +typedef struct _FbMqttClass FbMqttClass; +typedef struct _FbMqttPrivate FbMqttPrivate; +typedef struct _FbMqttMessage FbMqttMessage; +typedef struct _FbMqttMessageClass FbMqttMessageClass; +typedef struct _FbMqttMessagePrivate FbMqttMessagePrivate; +/** + * FbMqttConnectFlags: + * @FB_MQTT_CONNECT_FLAG_CLR: Clear the session. + * @FB_MQTT_CONNECT_FLAG_WILL: A will message is in the payload. + * @FB_MQTT_CONNECT_FLAG_RET: Retain the will message. + * @FB_MQTT_CONNECT_FLAG_PASS: A password is in the payload. + * @FB_MQTT_CONNECT_FLAG_USER: A user name is in the payload. + * @FB_MQTT_CONNECT_FLAG_QOS0: Use no quality of service. + * @FB_MQTT_CONNECT_FLAG_QOS1: Use level one quality of service. + * @FB_MQTT_CONNECT_FLAG_QOS2: Use level two quality of service. + * + * The #FbMqttMessage flags for the CONNECT message. + */ +typedef enum +{ + FB_MQTT_CONNECT_FLAG_CLR = 1 << 1, + FB_MQTT_CONNECT_FLAG_WILL = 1 << 2, + FB_MQTT_CONNECT_FLAG_RET = 1 << 5, + FB_MQTT_CONNECT_FLAG_PASS = 1 << 6, + FB_MQTT_CONNECT_FLAG_USER = 1 << 7, + FB_MQTT_CONNECT_FLAG_QOS0 = 0 << 3, + FB_MQTT_CONNECT_FLAG_QOS1 = 1 << 3, + FB_MQTT_CONNECT_FLAG_QOS2 = 2 << 3 +} FbMqttConnectFlags; /** - * The flags of #fb_mqtt CONNECT packets. - **/ -enum fb_mqtt_connect_flags + * FbMqttError: + * @FB_MQTT_ERROR_SUCCESS: There is no error. + * @FB_MQTT_ERROR_PRTVERS: Unacceptable protocol version. + * @FB_MQTT_ERROR_IDREJECT: Identifier rejected. + * @FB_MQTT_ERROR_SRVGONE: Server unavailable. + * @FB_MQTT_ERROR_USERPASS: Bad user name or password. + * @FB_MQTT_ERROR_UNAUTHORIZED: Not authorized. + * @FB_MQTT_ERROR_GENERAL: General failure. + * + * The error codes for the #FB_MQTT_ERROR domain. + */ +typedef enum { - FB_MQTT_CONNECT_FLAG_CLR = 1 << 1, /** Clear session. **/ - FB_MQTT_CONNECT_FLAG_WILL = 1 << 2, /** Will flag. **/ - FB_MQTT_CONNECT_FLAG_RET = 1 << 5, /** Will retain. **/ - FB_MQTT_CONNECT_FLAG_PASS = 1 << 6, /** Password. **/ - FB_MQTT_CONNECT_FLAG_USER = 1 << 7, /** Username. **/ - FB_MQTT_CONNECT_FLAG_QOS0 = 0 << 3, /** Fire and forget. **/ - FB_MQTT_CONNECT_FLAG_QOS1 = 1 << 3, /** Acknowledge delivery. **/ - FB_MQTT_CONNECT_FLAG_QOS2 = 2 << 3 /** Assure delivery. **/ -}; + FB_MQTT_ERROR_SUCCESS = 0, + FB_MQTT_ERROR_PRTVERS = 1, + FB_MQTT_ERROR_IDREJECT = 2, + FB_MQTT_ERROR_SRVGONE = 3, + FB_MQTT_ERROR_USERPASS = 4, + FB_MQTT_ERROR_UNAUTHORIZED = 5, + FB_MQTT_ERROR_GENERAL +} FbMqttError; /** - * The #GError codes of #fb_mqtt. - **/ -enum fb_mqtt_error + * FbMqttMessageFlags: + * @FB_MQTT_MESSAGE_FLAG_RET: Retain messages. + * @FB_MQTT_MESSAGE_FLAG_DUP: Duplicate delivery of control packet. + * @FB_MQTT_MESSAGE_FLAG_QOS0: Use no quality of service. + * @FB_MQTT_MESSAGE_FLAG_QOS1: Use level one quality of service. + * @FB_MQTT_MESSAGE_FLAG_QOS2: Use level two quality of service. + * + * The #FbMqttMessage flags. + */ +typedef enum { - FB_MQTT_ERROR_SUCCESS = 0, /** Success. **/ - FB_MQTT_ERROR_PRTVERS = 1, /** Unacceptable protocol version. **/ - FB_MQTT_ERROR_IDREJECT = 2, /** Identifier rejected. **/ - FB_MQTT_ERROR_SRVGONE = 3, /** Server unavailable. **/ - FB_MQTT_ERROR_USERPASS = 4, /** Bad username or password. **/ - FB_MQTT_ERROR_UNAUTHORIZED = 5, /** Not authorized. **/ - FB_MQTT_ERROR_GENERAL /** General. **/ -}; + FB_MQTT_MESSAGE_FLAG_RET = 1 << 0, + FB_MQTT_MESSAGE_FLAG_DUP = 1 << 3, + FB_MQTT_MESSAGE_FLAG_QOS0 = 0 << 1, + FB_MQTT_MESSAGE_FLAG_QOS1 = 1 << 1, + FB_MQTT_MESSAGE_FLAG_QOS2 = 2 << 1 +} FbMqttMessageFlags; /** - * The flags of #fb_mqtt messages. - **/ -enum fb_mqtt_msg_flags + * FbMqttMessageType: + * @FB_MQTT_MESSAGE_TYPE_CONNECT: Requests a connection. + * @FB_MQTT_MESSAGE_TYPE_CONNACK: Connection acknowledgment. + * @FB_MQTT_MESSAGE_TYPE_PUBLISH: Requests a message publication. + * @FB_MQTT_MESSAGE_TYPE_PUBACK: Publication acknowledgment. + * @FB_MQTT_MESSAGE_TYPE_PUBREC: Publication received. + * @FB_MQTT_MESSAGE_TYPE_PUBREL: Publication released. + * @FB_MQTT_MESSAGE_TYPE_PUBCOMP: Publication complete. + * @FB_MQTT_MESSAGE_TYPE_SUBSCRIBE: Requests a subscription. + * @FB_MQTT_MESSAGE_TYPE_SUBACK: Subscription acknowledgment. + * @FB_MQTT_MESSAGE_TYPE_UNSUBSCRIBE: Requests an unsubscription. + * @FB_MQTT_MESSAGE_TYPE_UNSUBACK: Unsubscription acknowledgment. + * @FB_MQTT_MESSAGE_TYPE_PINGREQ: Requests a ping response. + * @FB_MQTT_MESSAGE_TYPE_PINGRESP: Ping response. + * @FB_MQTT_MESSAGE_TYPE_DISCONNECT: Requests a disconnection. + * + * The #FbMqttMessage types. + */ +typedef enum { - FB_MQTT_MSG_FLAG_RET = 1 << 0, /** Retain. **/ - FB_MQTT_MSG_FLAG_DUP = 1 << 3, /** Duplicate delivery. **/ - FB_MQTT_MSG_FLAG_QOS0 = 0 << 1, /** Fire and forget. **/ - FB_MQTT_MSG_FLAG_QOS1 = 1 << 1, /** Acknowledge delivery. **/ - FB_MQTT_MSG_FLAG_QOS2 = 2 << 1 /** Assure delivery. **/ -}; + FB_MQTT_MESSAGE_TYPE_CONNECT = 1, + FB_MQTT_MESSAGE_TYPE_CONNACK = 2, + FB_MQTT_MESSAGE_TYPE_PUBLISH = 3, + FB_MQTT_MESSAGE_TYPE_PUBACK = 4, + FB_MQTT_MESSAGE_TYPE_PUBREC = 5, + FB_MQTT_MESSAGE_TYPE_PUBREL = 6, + FB_MQTT_MESSAGE_TYPE_PUBCOMP = 7, + FB_MQTT_MESSAGE_TYPE_SUBSCRIBE = 8, + FB_MQTT_MESSAGE_TYPE_SUBACK = 9, + FB_MQTT_MESSAGE_TYPE_UNSUBSCRIBE = 10, + FB_MQTT_MESSAGE_TYPE_UNSUBACK = 11, + FB_MQTT_MESSAGE_TYPE_PINGREQ = 12, + FB_MQTT_MESSAGE_TYPE_PINGRESP = 13, + FB_MQTT_MESSAGE_TYPE_DISCONNECT = 14 +} FbMqttMessageType; /** - * The type of #fb_mqtt messages. - **/ -enum fb_mqtt_msg_type + * FbMqtt: + * + * Represents an MQTT connection. + */ +struct _FbMqtt { - FB_MQTT_MSG_TYPE_CONNECT = 1, /** Connect to Server. **/ - FB_MQTT_MSG_TYPE_CONNACK = 2, /** Connect Acknowledgment. **/ - FB_MQTT_MSG_TYPE_PUBLISH = 3, /** Publish Message. **/ - FB_MQTT_MSG_TYPE_PUBACK = 4, /** Publish Acknowledgment. **/ - FB_MQTT_MSG_TYPE_PUBREC = 5, /** Publish Received. **/ - FB_MQTT_MSG_TYPE_PUBREL = 6, /** Publish Release. **/ - FB_MQTT_MSG_TYPE_PUBCOMP = 7, /** Publish Complete. **/ - FB_MQTT_MSG_TYPE_SUBSCRIBE = 8, /** Client Subscribe request. **/ - FB_MQTT_MSG_TYPE_SUBACK = 9, /** Subscribe Acknowledgment. **/ - FB_MQTT_MSG_TYPE_UNSUBSCRIBE = 10, /** Client Unsubscribe request. **/ - FB_MQTT_MSG_TYPE_UNSUBACK = 11, /** Unsubscribe Acknowledgment. **/ - FB_MQTT_MSG_TYPE_PINGREQ = 12, /** PING Request. **/ - FB_MQTT_MSG_TYPE_PINGRESP = 13, /** PING Response. **/ - FB_MQTT_MSG_TYPE_DISCONNECT = 14 /** Client is Disconnecting. **/ + /*< private >*/ + GObject parent; + FbMqttPrivate *priv; }; /** - * The main structure for #fb_mqtt callback functions. - **/ -struct fb_mqtt_funcs + * FbMqttClass: + * + * The base class for all #FbMqtt's. + */ +struct _FbMqttClass { - /** - * The error function. This is called whenever an error occurs - * within the #fb_mqtt. - * - * @param mqtt The #fb_mqtt. - * @param err The #GError. - * @param data The user-defined data or NULL. - **/ - void (*error) (fb_mqtt_t *mqtt, GError *err, gpointer data); - - /** - * The open function. This is called when the connection to the - * MQTT has been initialized. This is called as a result of - * #fb_mqtt_open(). This function should call #fb_mqtt_connect(). - * - * @param mqtt The #fb_mqtt. - * @param data The user-defined data or NULL. - **/ - void (*open) (fb_mqtt_t *mqtt, gpointer data); - - /** - * The connack function. This is called when a CONNACK packet is - * received. This is called as a result of #fb_mqtt_connect(). - * - * @param mqtt The #fb_mqtt. - * @param data The user-defined data or NULL. - **/ - void (*connack) (fb_mqtt_t *mqtt, gpointer data); - - /** - * The publish function. This is called when a PUBLISH packet is - * received. - * - * @param mqtt The #fb_mqtt. - * @param topic The message topic. - * @param pload The message payload. - * @param data The user-defined data or NULL. - **/ - void (*publish) (fb_mqtt_t *mqtt, const gchar *topic, - const GByteArray *pload, gpointer data); + /*< private >*/ + GObjectClass parent_class; }; /** - * The structure for interacting with Facebook MQTT. - **/ -struct fb_mqtt + * FbMqttMessage: + * + * Represents a reader/writer for an MQTT message. + */ +struct _FbMqttMessage { - gboolean connected; /** TRUE if connected, otherwise FALSE. **/ - - fb_mqtt_funcs_t funcs; /** The #fb_mqtt_funcs. **/ - gpointer data; /** The user defined data or NULL. **/ - - GError *err; /** The #GError or NULL. **/ - gpointer ssl; /** The SSL connection or NULL. **/ - gint tev; /** The timer event identifier. **/ - gint rev; /** The read event identifier. **/ - gint wev; /** The write event identifier. **/ - - GByteArray *rbuf; /** The read buffer. **/ - GByteArray *wbuf; /** The write buffer. **/ - gsize remz; /** The remaining read size. **/ - - guint16 mid; /** The message identifier. **/ + /*< private >*/ + GObject parent; + FbMqttMessagePrivate *priv; }; /** - * The structure of a #fb_mqtt message. - **/ -struct fb_mqtt_msg + * FbMqttMessageClass: + * + * The base class for all #FbMqttMessageClass's. + */ +struct _FbMqttMessageClass { - fb_mqtt_msg_type_t type; /** The #fb_mqtt_msg_type. **/ - fb_mqtt_msg_flags_t flags; /** The #fb_mqtt_msg_flags. **/ - - GByteArray *bytes; /** The #GByteArray of data. **/ - guint offset; /** The offset of the data. **/ - guint pos; /** The cursor position. **/ - - gboolean local; /** TRUE if the data is local. **/ + /*< private >*/ + GObjectClass parent_class; }; +/** + * fb_mqtt_get_type: + * + * Returns: The #GType for an #FbMqtt. + */ +GType +fb_mqtt_get_type(void); -#define FB_MQTT_ERROR fb_mqtt_error_quark() - -GQuark fb_mqtt_error_quark(void); +/** + * fb_mqtt_message_get_type: + * + * Returns: The #GType for an #FbMqttMessage. + */ +GType +fb_mqtt_message_get_type(void); -fb_mqtt_t *fb_mqtt_new(const fb_mqtt_funcs_t *funcs, gpointer data); +/** + * fb_mqtt_error_quark: + * + * Gets the #GQuark of the domain of MQTT errors. + * + * Returns: The #GQuark of the domain. + */ +GQuark +fb_mqtt_error_quark(void); -void fb_mqtt_free(fb_mqtt_t *mqtt); +/** + * fb_mqtt_ssl_error_quark: + * + * Gets the #GQuark of the domain of MQTT SSL errors. + * + * Returns: The #GQuark of the domain. + */ +GQuark +fb_mqtt_ssl_error_quark(void); -void fb_mqtt_close(fb_mqtt_t *mqtt); +/** + * fb_mqtt_new: + * + * Creates a new #FbMqtt. The returned #FbMqtt should be freed with + * #g_object_unref() when no longer needed. + * + * Returns: The new #FbMqtt. + */ +FbMqtt * +fb_mqtt_new(void); -void fb_mqtt_error(fb_mqtt_t *mqtt, fb_mqtt_error_t err, const gchar *fmt, ...) - G_GNUC_PRINTF(3, 4); +/** + * fb_mqtt_close: + * @mqtt: The #FbMqtt. + * + * Closes the MQTT without sending the `DISCONNECT` message. The #FbMqtt + * may be reopened after calling this. + */ +void +fb_mqtt_close(FbMqtt *mqtt); -void fb_mqtt_read(fb_mqtt_t *mqtt, fb_mqtt_msg_t *msg); +/** + * fb_mqtt_error: + * @mqtt: The #FbMqtt. + * @error: The #FbMqttError. + * @format: The format string literal. + * @...: The arguments for @format. + * + * Emits an #FbMqttError and closes the #FbMqtt. + */ +void +fb_mqtt_error(FbMqtt *mqtt, FbMqttError error, const gchar *format, ...) + G_GNUC_PRINTF(3, 4); -void fb_mqtt_write(fb_mqtt_t *mqtt, fb_mqtt_msg_t *msg); +/** + * fb_mqtt_read: + * @mqtt: The #FbMqtt. + * @msg: The #FbMqttMessage. + * + * Reads an #FbMqttMessage into the #FbMqtt for processing. + */ +void +fb_mqtt_read(FbMqtt *mqtt, FbMqttMessage *msg); -void fb_mqtt_open(fb_mqtt_t *mqtt, const gchar *host, gint port); +/** + * fb_mqtt_write: + * @mqtt: The #FbMqtt. + * @msg: The #FbMqttMessage. + * + * Writes an #FbMqttMessage to the wire. + */ +void +fb_mqtt_write(FbMqtt *mqtt, FbMqttMessage *msg); -void fb_mqtt_connect(fb_mqtt_t *mqtt, guint8 flags, const gchar *cid, ...) - G_GNUC_NULL_TERMINATED; +/** + * fb_mqtt_open: + * @mqtt: The #FbMqtt. + * @host: The host name. + * @port: The port. + * + * Opens an SSL connection to the remote server. + */ +void +fb_mqtt_open(FbMqtt *mqtt, const gchar *host, gint port); -gboolean fb_mqtt_connected(fb_mqtt_t *mqtt, gboolean error); +/** + * fb_mqtt_connect: + * @mqtt: The #FbMqtt. + * @flags: The #FbMqttConnectFlags. + * @pload: The payload. + * + * Sends a message of type #FB_MQTT_MESSAGE_TYPE_CONNECT. + */ +void +fb_mqtt_connect(FbMqtt *mqtt, guint8 flags, const GByteArray *pload); -void fb_mqtt_disconnect(fb_mqtt_t *mqtt); +/** + * fb_mqtt_connected: + * @mqtt: The #FbMqtt. + * @error: #TRUE to error with no connection, otherwise #FALSE. + * + * Determines the connection state of the #FbMqtt, and optionally emits + * an error. + * + * Returns: #TRUE if the #FbMqtt is connected, otherwise #FALSE. + */ +gboolean +fb_mqtt_connected(FbMqtt *mqtt, gboolean error); -void fb_mqtt_publish(fb_mqtt_t *mqtt, const gchar *topic, - const GByteArray *bytes); +/** + * fb_mqtt_disconnect: + * @mqtt: The #FbMqtt. + * + * Sends a message of type #FB_MQTT_MESSAGE_TYPE_DISCONNECT, and closes + * the connection. + */ +void +fb_mqtt_disconnect(FbMqtt *mqtt); -void fb_mqtt_subscribe(fb_mqtt_t *mqtt, const gchar *topic1, guint16 qos1, ...) - G_GNUC_NULL_TERMINATED; +/** + * fb_mqtt_publish: + * @mqtt: The #FbMqtt. + * @topic: The topic. + * @pload: The payload. + * + * Sends a message of type #FB_MQTT_MESSAGE_TYPE_PUBLISH. + */ +void +fb_mqtt_publish(FbMqtt *mqtt, const gchar *topic, const GByteArray *pload); -void fb_mqtt_unsubscribe(fb_mqtt_t *mqtt, const gchar *topic1, ...) - G_GNUC_NULL_TERMINATED; +/** + * fb_mqtt_subscribe: + * @mqtt: The #FbMqtt. + * @topic1: The first topic. + * @qos1: The first QoS. + * @...: The %NULL-terminated list of topic/QoS pairs. + * + * Sends a message of type #FB_MQTT_MESSAGE_TYPE_SUBSCRIBE. + */ +void +fb_mqtt_subscribe(FbMqtt *mqtt, const gchar *topic1, guint16 qos1, ...) + G_GNUC_NULL_TERMINATED; -fb_mqtt_msg_t *fb_mqtt_msg_new(fb_mqtt_msg_type_t type, - fb_mqtt_msg_flags_t flags); +/** + * fb_mqtt_unsubscribe: + * @mqtt: The #FbMqtt. + * @topic1: The first topic. + * @...: The %NULL-terminated list of topics. + * + * Sends a message of type #FB_MQTT_MESSAGE_TYPE_UNSUBSCRIBE. + */ +void +fb_mqtt_unsubscribe(FbMqtt *mqtt, const gchar *topic1, ...) + G_GNUC_NULL_TERMINATED; -fb_mqtt_msg_t *fb_mqtt_msg_new_bytes(GByteArray *bytes); +/** + * fb_mqtt_message_new: + * @type: The #FbMqttMessageType. + * @flags: The #FbMqttMessageFlags. + * + * Creates a new #FbMqttMessage. The returned #FbMqttMessage should be + * freed with #g_object_unref() when no longer needed. + * + * Returns: The new #FbMqttMessage. + */ +FbMqttMessage * +fb_mqtt_message_new(FbMqttMessageType type, FbMqttMessageFlags flags); -void fb_mqtt_msg_free(fb_mqtt_msg_t *msg); +/** + * fb_mqtt_message_new_bytes: + * @bytes: The #GByteArray. + * + * Creates a new #FbMqttMessage from a #GByteArray. The returned + * #FbMqttMessage should be freed with #g_object_unref() when no + * longer needed. + * + * Returns: The new #FbMqttMessage. + */ +FbMqttMessage * +fb_mqtt_message_new_bytes(GByteArray *bytes); -void fb_mqtt_msg_reset(fb_mqtt_msg_t *msg); +/** + * fb_mqtt_message_reset: + * @msg: The #FbMqttMessage. + * + * Resets an #FbMqttMessage. This resets the cursor position, and + * removes any sort of fixed header. + */ +void +fb_mqtt_message_reset(FbMqttMessage *msg); -const GByteArray *fb_mqtt_msg_bytes(fb_mqtt_msg_t *msg); +/** + * fb_mqtt_message_bytes: + * @msg: The #FbMqttMessage. + * + * Formats the internal #GByteArray of the #FbMqttMessage with the + * required fixed header. This resets the cursor position. + * + * Returns: The internal #GByteArray. + */ +const GByteArray * +fb_mqtt_message_bytes(FbMqttMessage *msg); -gboolean fb_mqtt_msg_read(fb_mqtt_msg_t *msg, gpointer data, guint size); +/** + * fb_mqtt_message_read: + * @msg: The #FbMqttMessage. + * @data: The data buffer. + * @size: The size of @buffer. + * + * Reads data from the #FbMqttMessage into a buffer. If @data is #NULL, + * this will simply advance the cursor position. + * + * Returns: #TRUE if the data was read, otherwise #FALSE. + */ +gboolean +fb_mqtt_message_read(FbMqttMessage *msg, gpointer data, guint size); -gboolean fb_mqtt_msg_read_r(fb_mqtt_msg_t *msg, GByteArray *bytes); +/** + * fb_mqtt_message_read_r: + * @msg: The #FbMqttMessage. + * @bytes: The #GByteArray. + * + * Reads the remaining data from the #FbMqttMessage into a #GByteArray. + * This is useful for obtaining the payload of a message. + * + * Returns: #TRUE if the data was read, otherwise #FALSE. + */ +gboolean +fb_mqtt_message_read_r(FbMqttMessage *msg, GByteArray *bytes); -gboolean fb_mqtt_msg_read_byte(fb_mqtt_msg_t *msg, guint8 *byte); +/** + * fb_mqtt_message_read_byte: + * @msg: The #FbMqttMessage. + * @value: The return location for the value, or #NULL. + * + * Reads an 8-bit integer value from the #FbMqttMessage. If @value is + * #NULL, this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_mqtt_message_read_byte(FbMqttMessage *msg, guint8 *value); -gboolean fb_mqtt_msg_read_mid(fb_mqtt_msg_t *msg, guint16 *mid); +/** + * fb_mqtt_message_read_mid: + * @msg: The #FbMqttMessage. + * @value: The return location for the value, or #NULL. + * + * Reads a message identifier from the #FbMqttMessage. If @value is + * #NULL, this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_mqtt_message_read_mid(FbMqttMessage *msg, guint16 *value); -gboolean fb_mqtt_msg_read_u16(fb_mqtt_msg_t *msg, guint16 *u16); +/** + * fb_mqtt_message_read_u16: + * @msg: The #FbMqttMessage. + * @value: The return location for the value, or #NULL. + * + * Reads a 16-bit integer value from the #FbMqttMessage. If @value is + * #NULL, this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_mqtt_message_read_u16(FbMqttMessage *msg, guint16 *value); -gboolean fb_mqtt_msg_read_str(fb_mqtt_msg_t *msg, gchar **str); +/** + * fb_mqtt_message_read_str: + * @msg: The #FbMqttMessage. + * @value: The return location for the value, or #NULL. + * + * Reads a string value from the #FbMqttMessage. The value returned to + * @value should be freed with #g_free() when no longer needed. If + * @value is #NULL, this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_mqtt_message_read_str(FbMqttMessage *msg, gchar **value); -void fb_mqtt_msg_write(fb_mqtt_msg_t *msg, gconstpointer data, guint size); +/** + * fb_mqtt_message_write: + * @msg: The #FbMqttMessage. + * @data: The data buffer. + * @size: The size of @buffer. + * + * Writes data to the #FbMqttMessage. + */ +void +fb_mqtt_message_write(FbMqttMessage *msg, gconstpointer data, guint size); -void fb_mqtt_msg_write_byte(fb_mqtt_msg_t *msg, guint8 byte); +/** + * fb_mqtt_message_write_byte: + * @msg: The #FbMqttMessage. + * @value: The value. + * + * Writes an 8-bit integer value to the #FbMqttMessage. + */ +void +fb_mqtt_message_write_byte(FbMqttMessage *msg, guint8 value); -void fb_mqtt_msg_write_mid(fb_mqtt_msg_t *msg, guint16 *mid); +/** + * fb_mqtt_message_write_mid: + * @msg: The #FbMqttMessage. + * @value: The value. + * + * Writes a message identifier to the #FbMqttMessage. This increments + * @value for the next message. + */ +void +fb_mqtt_message_write_mid(FbMqttMessage *msg, guint16 *value); -void fb_mqtt_msg_write_u16(fb_mqtt_msg_t *msg, guint16 u16); +/** + * fb_mqtt_message_write_u16: + * @msg: The #FbMqttMessage. + * @value: The value. + * + * Writes a 16-bit integer value to the #FbMqttMessage. + */ +void +fb_mqtt_message_write_u16(FbMqttMessage *msg, guint16 value); -void fb_mqtt_msg_write_str(fb_mqtt_msg_t *msg, const gchar *str); +/** + * fb_mqtt_message_write_str: + * @msg: The #FbMqttMessage. + * @value: The value. + * + * Writes a string value to the #FbMqttMessage. + */ +void +fb_mqtt_message_write_str(FbMqttMessage *msg, const gchar *value); -#endif /* _FACEBOOK_MQTT_H */ +#endif /* _FACEBOOK_MQTT_H_ */ diff --git a/facebook/facebook-thrift.c b/facebook/facebook-thrift.c index ec49333..65fa7c8 100644 --- a/facebook/facebook-thrift.c +++ b/facebook/facebook-thrift.c @@ -19,425 +19,298 @@ #include "facebook-thrift.h" -/** - * Creates a new #fb_thrift. The returned #fb_thrift should be freed - * with #fb_thrift_free() when no longer needed. If #GByteArray passed - * to this function is not NULL, then it MUST exist for the lifetime - * of the #fb_thrift. - * - * @param bytes The #GByteArray or NULL. - * @param offset The data offset. - * @param compact TRUE for compact types. - * - * @return The #fb_thrift or NULL on error. - **/ -fb_thrift_t *fb_thrift_new(GByteArray *bytes, guint offset, gboolean compact) +struct _FbThriftPrivate { - fb_thrift_t *thft; + GByteArray *bytes; + gboolean internal; + guint offset; + guint pos; + guint lastbool; + gint16 lastid; +}; - thft = g_new0(fb_thrift_t, 1); +G_DEFINE_TYPE(FbThrift, fb_thrift, G_TYPE_OBJECT); - if (bytes == NULL) { - thft->bytes = g_byte_array_new(); - thft->flags |= FB_THRIFT_FLAG_INTERNAL; - } else { - thft->bytes = bytes; - thft->offset = offset; +static void +fb_thrift_dispose(GObject *obj) +{ + FbThriftPrivate *priv = FB_THRIFT(obj)->priv; + + if (priv->internal) { + g_byte_array_free(priv->bytes, TRUE); } +} - if (compact) - thft->flags |= FB_THRIFT_FLAG_COMPACT; +static void +fb_thrift_class_init(FbThriftClass *klass) +{ + GObjectClass *gklass = G_OBJECT_CLASS(klass); - thft->pos = thft->offset; - return thft; + gklass->dispose = fb_thrift_dispose; + g_type_class_add_private(klass, sizeof (FbThriftPrivate)); } -/** - * Frees all memory used by a #fb_thrift. - * - * @param thft The #fb_thrift. - **/ -void fb_thrift_free(fb_thrift_t *thft) +static void +fb_thrift_init(FbThrift *thft) { - if (G_UNLIKELY(thft == NULL)) - return; + FbThriftPrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE(thft, FB_TYPE_THRIFT, + FbThriftPrivate); + thft->priv = priv; +} + +FbThrift * +fb_thrift_new(GByteArray *bytes, guint offset) +{ + FbThrift *thft; + FbThriftPrivate *priv; - if (thft->flags & FB_THRIFT_FLAG_INTERNAL) - g_byte_array_free(thft->bytes, TRUE); + thft = g_object_new(FB_TYPE_THRIFT, NULL); + priv = thft->priv; - g_free(thft); + if (bytes != NULL) { + priv->bytes = bytes; + priv->offset = offset; + priv->pos = offset; + } else { + priv->bytes = g_byte_array_new(); + priv->internal = TRUE; + } + + return thft; } -/** - * Frees all memory used by a #fb_thrift. - * - * @param thft The #fb_thrift. - **/ -void fb_thrift_reset(fb_thrift_t *thft) +const GByteArray * +fb_thrift_get_bytes(FbThrift *thft) { - g_return_if_fail(thft != NULL); + FbThriftPrivate *priv; - thft->pos = thft->offset; + g_return_val_if_fail(FB_IS_THRIFT(thft), NULL); + priv = thft->priv; + return priv->bytes; } -/** - * Reads raw data from a #fb_thrift. If the return location is NULL, - * only the cursor is advanced. - * - * @param thft The #fb_thrift. - * @param data The data buffer or NULL. - * @param size The size of data to read. - * - * @return TRUE if the data was completely read, otherwise FALSE. - **/ -gboolean fb_thrift_read(fb_thrift_t *thft, gpointer data, guint size) +guint +fb_thrift_get_pos(FbThrift *thft) { - g_return_val_if_fail(thft != NULL, FALSE); + FbThriftPrivate *priv; - if ((thft->pos + size) > thft->bytes->len) - return FALSE; + g_return_val_if_fail(FB_IS_THRIFT(thft), 0); + priv = thft->priv; + return priv->pos; +} - if ((data != NULL) && (size > 0)) - memcpy(data, thft->bytes->data + thft->pos, size); +void +fb_thrift_set_pos(FbThrift *thft, guint pos) +{ + FbThriftPrivate *priv; - thft->pos += size; - return TRUE; + g_return_if_fail(FB_IS_THRIFT(thft)); + priv = thft->priv; + priv->pos = pos; } -/** - * Reads a boolean from a #fb_thrift. If the return location is NULL, - * only the cursor is advanced. - * - * @param thft The #fb_thrift. - * @param bln The return location for the boolean or NULL. - * - * @return TRUE if the boolean was completely read, otherwise FALSE. - **/ -gboolean fb_thrift_read_bool(fb_thrift_t *thft, gboolean *bln) +void +fb_thrift_reset(FbThrift *thft) { - guint8 byte; + FbThriftPrivate *priv; - g_return_val_if_fail(thft != NULL, FALSE); + g_return_if_fail(FB_IS_THRIFT(thft)); + priv = thft->priv; + priv->pos = priv->offset; +} - if (bln != NULL) - *bln = FALSE; +gboolean +fb_thrift_read(FbThrift *thft, gpointer data, guint size) +{ + FbThriftPrivate *priv; - if (!(thft->flags & FB_THRIFT_FLAG_COMPACT)) { - if (!fb_thrift_read_byte(thft, &byte)) - return FALSE; + g_return_val_if_fail(FB_IS_THRIFT(thft), FALSE); + priv = thft->priv; - if (bln != NULL) - *bln = byte != 0; + if ((priv->pos + size) > priv->bytes->len) { + return FALSE; + } - return TRUE; + if ((data != NULL) && (size > 0)) { + memcpy(data, priv->bytes->data + priv->pos, size); } - if ((thft->lastbool & 0x03) != 0x01) { - if (!fb_thrift_read_byte(thft, &byte)) + priv->pos += size; + return TRUE; +} + +gboolean +fb_thrift_read_bool(FbThrift *thft, gboolean *value) +{ + FbThriftPrivate *priv; + guint8 byte; + + g_return_val_if_fail(FB_IS_THRIFT(thft), FALSE); + priv = thft->priv; + + if ((priv->lastbool & 0x03) != 0x01) { + if (!fb_thrift_read_byte(thft, &byte)) { return FALSE; + } - if (bln != NULL) - *bln = (byte & 0x0F) == 0x01; + if (value != NULL) { + *value = (byte & 0x0F) == 0x01; + } + priv->lastbool = 0; return TRUE; } - if (bln != NULL) - *bln = ((thft->lastbool & 0x04) >> 2) != 0; + if (value != NULL) { + *value = ((priv->lastbool & 0x04) >> 2) != 0; + } - thft->lastbool = 0; + priv->lastbool = 0; return TRUE; } -/** - * Reads a single byte from a #fb_thrift. If the return location is - * NULL, only the cursor is advanced. - * - * @param thft The #fb_thrift. - * @param byte The return location for the byte or NULL. - * - * @return TRUE if the byte was completely read, otherwise FALSE. - **/ -gboolean fb_thrift_read_byte(fb_thrift_t *thft, guint8 *byte) +gboolean +fb_thrift_read_byte(FbThrift *thft, guint8 *value) { - if (byte != NULL) - *byte = 0; - - return fb_thrift_read(thft, byte, sizeof *byte); + return fb_thrift_read(thft, value, sizeof *value); } -/** - * Reads a double from a #fb_thrift. If the return location is NULL, - * only the cursor is advanced. - * - * @param thft The #fb_thrift. - * @param dbl The return location for the double or NULL. - * - * @return TRUE if the double was completely read, otherwise FALSE. - **/ -gboolean fb_thrift_read_dbl(fb_thrift_t *thft, gdouble *dbl) +gboolean +fb_thrift_read_dbl(FbThrift *thft, gdouble *value) { gint64 i64; /* Almost always 8, but check anyways */ - static const gsize size = MIN(sizeof dbl, sizeof i64); - - if (dbl != NULL) - *dbl = 0; + static const gsize size = MIN(sizeof value, sizeof i64); - if (!fb_thrift_read_i64(thft, &i64)) + if (!fb_thrift_read_i64(thft, &i64)) { return FALSE; + } - if (dbl != NULL) - memcpy(&dbl, &i64, size); + if (value != NULL) { + memcpy(value, &i64, size); + } return TRUE; } -/** - * Reads a 16-bit integer from a #fb_thrift. If the #fb_thrift is in - * compact mode, this will convert the integer from the zigzag format - * after reading. If the return location is NULL, only the cursor is - * advanced. - * - * @param thft The #fb_thrift. - * @param i16 The return location for the integer or NULL. - * - * @return TRUE if the integer was completely read, otherwise FALSE. - **/ -gboolean fb_thrift_read_i16(fb_thrift_t *thft, gint16 *i16) +gboolean +fb_thrift_read_i16(FbThrift *thft, gint16 *value) { gint64 i64; - g_return_val_if_fail(thft != NULL, FALSE); - - if (i16 != NULL) - *i16 = 0; - - if (!(thft->flags & FB_THRIFT_FLAG_COMPACT)) { - if (!fb_thrift_read(thft, i16, sizeof *i16)) - return FALSE; - - if (i16 != NULL) - *i16 = GINT16_FROM_BE(*i16); - - return TRUE; - } - - if (!fb_thrift_read_i64(thft, &i64)) + if (!fb_thrift_read_i64(thft, &i64)) { return FALSE; + } - if (i16 != NULL) - *i16 = i64; + if (value != NULL) { + *value = i64; + } return TRUE; } -/** - * Reads a 16-bit variable integer from a #fb_thrift. This function - * only reads if the #fb_thrift is in compact mode. This only reads - * the raw integer value without converting from the zigzag format. - * If the return location is NULL, only the cursor is advanced. - * - * @param thft The #fb_thrift. - * @param u16 The return location for the integer or NULL. - * - * @return TRUE if the integer was completely read, otherwise FALSE. - **/ -gboolean fb_thrift_read_vi16(fb_thrift_t *thft, guint16 *u16) +gboolean +fb_thrift_read_vi16(FbThrift *thft, guint16 *value) { guint64 u64; - if (u16 != NULL) - *u16 = 0; - - if (!fb_thrift_read_vi64(thft, &u64)) + if (!fb_thrift_read_vi64(thft, &u64)) { return FALSE; + } - if (u16 != NULL) - *u16 = u64; + if (value != NULL) { + *value = u64; + } return TRUE; } -/** - * Reads a 32-bit integer from a #fb_thrift. If the #fb_thrift is in - * compact mode, this will convert the integer from the zigzag format - * after reading. If the return location is NULL, only the cursor is - * advanced. - * - * @param thft The #fb_thrift. - * @param i32 The return location for the integer or NULL. - * - * @return TRUE if the integer was completely read, otherwise FALSE. - **/ -gboolean fb_thrift_read_i32(fb_thrift_t *thft, gint32 *i32) +gboolean +fb_thrift_read_i32(FbThrift *thft, gint32 *value) { gint64 i64; - g_return_val_if_fail(thft != NULL, FALSE); - - if (i32 != NULL) - *i32 = 0; - - if (!(thft->flags & FB_THRIFT_FLAG_COMPACT)) { - if (!fb_thrift_read(thft, i32, sizeof *i32)) - return FALSE; - - if (i32 != NULL) - *i32 = GINT32_FROM_BE(*i32); - - return TRUE; - } - - if (!fb_thrift_read_i64(thft, &i64)) + if (!fb_thrift_read_i64(thft, &i64)) { return FALSE; + } - if (i32 != NULL) - *i32 = i64; + if (value != NULL) { + *value = i64; + } return TRUE; } -/** - * Reads a 32-bit variable integer from a #fb_thrift. This function - * only reads if the #fb_thrift is in compact mode. This only reads - * the raw integer value without converting from the zigzag format. - * If the return location is NULL, only the cursor is advanced. - * - * @param thft The #fb_thrift. - * @param u32 The return location for the integer or NULL. - * - * @return TRUE if the integer was completely read, otherwise FALSE. - **/ -gboolean fb_thrift_read_vi32(fb_thrift_t *thft, guint32 *u32) +gboolean +fb_thrift_read_vi32(FbThrift *thft, guint32 *value) { guint64 u64; - if (u32 != NULL) - *u32 = 0; - - if (!fb_thrift_read_vi64(thft, &u64)) + if (!fb_thrift_read_vi64(thft, &u64)) { return FALSE; + } - if (u32 != NULL) - *u32 = u64; + if (value != NULL) { + *value = u64; + } return TRUE; } -/** - * Reads a 64-bit integer from a #fb_thrift. If the #fb_thrift is in - * compact mode, this will convert the integer from the zigzag format - * after reading. If the return location is NULL, only the cursor is - * advanced. - * - * @param thft The #fb_thrift. - * @param i64 The return location for the integer or NULL. - * - * @return TRUE if the integer was completely read, otherwise FALSE. - **/ -gboolean fb_thrift_read_i64(fb_thrift_t *thft, gint64 *i64) +gboolean +fb_thrift_read_i64(FbThrift *thft, gint64 *value) { guint64 u64; - g_return_val_if_fail(thft != NULL, FALSE); - - if (i64 != NULL) - *i64 = 0; - - if (!(thft->flags & FB_THRIFT_FLAG_COMPACT)) { - if (!fb_thrift_read(thft, i64, sizeof *i64)) - return FALSE; - - if (i64 != NULL) - *i64 = GINT64_FROM_BE(*i64); - - return TRUE; - } - - if (!fb_thrift_read_vi64(thft, &u64)) + if (!fb_thrift_read_vi64(thft, &u64)) { return FALSE; + } - if (i64 != NULL) { + if (value != NULL) { /* Convert from zigzag to integer */ - *i64 = (u64 >> 0x01) ^ -(u64 & 0x01); + *value = (u64 >> 0x01) ^ -(u64 & 0x01); } return TRUE; } -/** - * Reads a 64-bit variable integer from a #fb_thrift. This function - * only reads if the #fb_thrift is in compact mode. This only reads - * the raw integer value without converting from the zigzag format. - * If the return location is NULL, only the cursor is advanced. - * - * @param thft The #fb_thrift. - * @param u64 The return location for the integer or NULL. - * - * @return TRUE if the integer was completely read, otherwise FALSE. - **/ -gboolean fb_thrift_read_vi64(fb_thrift_t *thft, guint64 *u64) +gboolean +fb_thrift_read_vi64(FbThrift *thft, guint64 *value) { + guint i = 0; guint8 byte; - guint i; - - g_return_val_if_fail(thft != NULL, FALSE); - - if (u64 != NULL) { - *u64 = 0; - i = 0; - } - - if (!(thft->flags & FB_THRIFT_FLAG_COMPACT)) - return FALSE; + guint64 u64 = 0; do { if (!fb_thrift_read_byte(thft, &byte)) { - if (u64 != NULL) - *u64 = 0; - return FALSE; } - if (u64 != NULL) { - *u64 |= ((guint64) (byte & 0x7F)) << i; - i += 7; - } + u64 |= ((guint64) (byte & 0x7F)) << i; + i += 7; } while ((byte & 0x80) == 0x80); + if (value != NULL) { + *value = u64; + } + return TRUE; } -/** - * Reads a string from a #fb_thrift. If the return location is NULL, - * only the cursor is advanced. The returned string should be freed - * with #g_free() when no longer needed. - * - * @param thft The #fb_thrift. - * @param str The return location for the string or NULL. - * - * @return TRUE if the string was completely read, otherwise FALSE. - **/ -gboolean fb_thrift_read_str(fb_thrift_t *thft, gchar **str) +gboolean +fb_thrift_read_str(FbThrift *thft, gchar **value) { - guint32 size; - guint8 *data; - gboolean res; - - if (str != NULL) - *str = NULL; - - if (thft->flags & FB_THRIFT_FLAG_COMPACT) - res = fb_thrift_read_vi32(thft, &size); - else - res = fb_thrift_read_i32(thft, (gint32*) &size); + guint8 *data; + guint32 size; - if (!res) + if (!fb_thrift_read_vi32(thft, &size)) { return FALSE; + } - if (str != NULL) { + if (value != NULL) { data = g_new(guint8, size + 1); data[size] = 0; } else { @@ -449,89 +322,64 @@ gboolean fb_thrift_read_str(fb_thrift_t *thft, gchar **str) return FALSE; } - if (str != NULL) - *str = (gchar*) data; + if (value != NULL) { + *value = (gchar*) data; + } return TRUE; } -/** - * Reads a field header from a #fb_thrift. - * - * @param thft The #fb_thrift. - * @param type The return location for the #fb_thrift_type. - * @param id The return location for the identifier or NULL. - * - * @return TRUE if the header was completely read, otherwise FALSE. - **/ -gboolean fb_thrift_read_field(fb_thrift_t *thft, fb_thrift_type_t *type, - gint16 *id) +gboolean +fb_thrift_read_field(FbThrift *thft, FbThriftType *type, gint16 *id) { - guint8 byte; + FbThriftPrivate *priv; gint16 i16; + guint8 byte; - g_return_val_if_fail(thft != NULL, FALSE); + g_return_val_if_fail(FB_IS_THRIFT(thft), FALSE); g_return_val_if_fail(type != NULL, FALSE); - - if (id != NULL) - *id = 0; + priv = thft->priv; if (!fb_thrift_read_byte(thft, &byte)) { - *type = 0; return FALSE; } if (byte == FB_THRIFT_TYPE_STOP) { - *type = byte; + *type = FB_THRIFT_TYPE_STOP; return FALSE; } - if (!(thft->flags & FB_THRIFT_FLAG_COMPACT)) { - *type = byte; - - if (!fb_thrift_read_i16(thft, &i16)) - return FALSE; - - if (id != NULL) - *id = i16; - - return TRUE; - } - *type = fb_thrift_ct2t(byte & 0x0F); - i16 = (byte & 0xF0) >> 4; + i16 = (byte & 0xF0) >> 4; if (*type == FB_THRIFT_TYPE_BOOL) { - thft->lastbool = 0x01; + priv->lastbool = 0x01; - if ((byte & 0x0F) == 0x01) - thft->lastbool |= 0x01 << 2; + if ((byte & 0x0F) == 0x01) { + priv->lastbool |= 0x01 << 2; + } return TRUE; } if (i16 == 0) { - if (!fb_thrift_read_i16(thft, &i16)) + if (!fb_thrift_read_i16(thft, &i16)) { return FALSE; + } } else { - i16 = thft->lastid + i16; + i16 = priv->lastid + i16; } - if (id != NULL) + if (id != NULL) { *id = i16; + } - thft->lastid = i16; + priv->lastid = i16; return TRUE; } -/** - * Reads a field stop from a #fb_thrift. - * - * @param thft The #fb_thrift. - * - * @return TRUE if the stop was completely read, otherwise FALSE. - **/ -gboolean fb_thrift_read_stop(fb_thrift_t *thft) +gboolean +fb_thrift_read_stop(FbThrift *thft) { guint8 byte; @@ -539,66 +387,43 @@ gboolean fb_thrift_read_stop(fb_thrift_t *thft) (byte == FB_THRIFT_TYPE_STOP); } -/** - * Determines if the next byte is a field stop without advancing the - * cursor. - * - * @param thft The #fb_thrift. - * - * @return TRUE if the next byte is a field stop, otherwise FALSE. - **/ -gboolean fb_thrift_read_isstop(fb_thrift_t *thft) +gboolean +fb_thrift_read_isstop(FbThrift *thft) { + FbThriftPrivate *priv; guint8 byte; - if (!fb_thrift_read_byte(thft, &byte)) + g_return_val_if_fail(FB_IS_THRIFT(thft), FALSE); + priv = thft->priv; + + if (!fb_thrift_read_byte(thft, &byte)) { return FALSE; + } - thft->pos--; + priv->pos--; return byte == FB_THRIFT_TYPE_STOP; } -/** - * Reads a list header from a #fb_thrift. - * - * @param thft The #fb_thrift. - * @param type The return location for the #fb_thrift_type. - * @param size The return location for the size. - * - * @return TRUE if the header was completely read, otherwise FALSE. - **/ -gboolean fb_thrift_read_list(fb_thrift_t *thft, fb_thrift_type_t *type, - guint *size) +gboolean +fb_thrift_read_list(FbThrift *thft, FbThriftType *type, guint *size) { - guint8 byte; - gint32 i32; + guint8 byte; guint32 u32; - g_return_val_if_fail(thft != NULL, FALSE); g_return_val_if_fail(type != NULL, FALSE); g_return_val_if_fail(size != NULL, FALSE); - *type = 0; - *size = 0; - - if (!fb_thrift_read_byte(thft, &byte)) + if (!fb_thrift_read_byte(thft, &byte)) { return FALSE; - - if (!(thft->flags & FB_THRIFT_FLAG_COMPACT)) { - if (!fb_thrift_read_i32(thft, &i32)) - return FALSE; - - *type = byte; - *size = i32; - return TRUE; } *type = fb_thrift_ct2t(byte & 0x0F); *size = (byte & 0xF0) >> 4; - if (*size == 15) { - if (!fb_thrift_read_vi32(thft, &u32)) + if (*size == 0x0F) { + if (!fb_thrift_read_vi32(thft, &u32)) { return FALSE; + } *size = u32; } @@ -606,57 +431,25 @@ gboolean fb_thrift_read_list(fb_thrift_t *thft, fb_thrift_type_t *type, return TRUE; } -/** - * Reads a map header from a #fb_thrift. - * - * @param thft The #fb_thrift. - * @param ktype The return location for the key #fb_thrift_type. - * @param vtype The return location for the value #fb_thrift_type. - * @param size The return location for the size. - * - * @return TRUE if the header was completely read, otherwise FALSE. - **/ -gboolean fb_thrift_read_map(fb_thrift_t *thft, fb_thrift_type_t *ktype, - fb_thrift_type_t *vtype, guint *size) +gboolean +fb_thrift_read_map(FbThrift *thft, FbThriftType *ktype, FbThriftType *vtype, + guint *size) { - guint8 byte; gint32 i32; + guint8 byte; - g_return_val_if_fail(thft != NULL, FALSE); g_return_val_if_fail(ktype != NULL, FALSE); g_return_val_if_fail(vtype != NULL, FALSE); - g_return_val_if_fail(size != NULL, FALSE); - - *ktype = 0; - *vtype = 0; - *size = 0; - - if (!(thft->flags & FB_THRIFT_FLAG_COMPACT)) { - if (!fb_thrift_read_byte(thft, &byte)) - return FALSE; - - *ktype = byte; - - if (!fb_thrift_read_byte(thft, &byte)) - return FALSE; - - *vtype = byte; - - if (!fb_thrift_read_i32(thft, &i32)) - return FALSE; - - *size = i32; - return TRUE; - } + g_return_val_if_fail(size != NULL, FALSE); - if (!fb_thrift_read_i32(thft, &i32)) + if (!fb_thrift_read_i32(thft, &i32)) { return FALSE; + } - *size = i32; - - if (*size != 0) { - if (!fb_thrift_read_byte(thft, &byte)) + if (i32 != 0) { + if (!fb_thrift_read_byte(thft, &byte)) { return FALSE; + } *ktype = fb_thrift_ct2t((byte & 0xF0) >> 4); *vtype = fb_thrift_ct2t(byte & 0x0F); @@ -665,301 +458,167 @@ gboolean fb_thrift_read_map(fb_thrift_t *thft, fb_thrift_type_t *ktype, *vtype = 0; } + *size = i32; return TRUE; } -/** - * Reads a set header from a #fb_thrift. - * - * @param thft The #fb_thrift. - * @param type The return location for the #fb_thrift_type. - * @param size The return location for the size. - * - * @return TRUE if the header was completely read, otherwise FALSE. - **/ -gboolean fb_thrift_read_set(fb_thrift_t *thft, fb_thrift_type_t *type, - guint *size) +gboolean +fb_thrift_read_set(FbThrift *thft, FbThriftType *type, guint *size) { return fb_thrift_read_list(thft, type, size); } -/** - * Writes raw data to a #fb_thrift. - * - * @param thft The #fb_thrift. - * @param data The data. - * @param size The size of the data. - **/ -void fb_thrift_write(fb_thrift_t *thft, gconstpointer data, guint size) +void +fb_thrift_write(FbThrift *thft, gconstpointer data, guint size) { - g_return_if_fail(thft != NULL); + FbThriftPrivate *priv; + + g_return_if_fail(FB_IS_THRIFT(thft)); + priv = thft->priv; - g_byte_array_append(thft->bytes, data, size); - thft->pos += size; + g_byte_array_append(priv->bytes, data, size); + priv->pos += size; } -/** - * Writes a boolean to a #fb_thrift. - * - * @param thft The #fb_thrift. - * @param bln The boolean. - **/ -void fb_thrift_write_bool(fb_thrift_t *thft, gboolean bln) +void +fb_thrift_write_bool(FbThrift *thft, gboolean value) { + FbThriftPrivate *priv; guint pos; - g_return_if_fail(thft != NULL); + g_return_if_fail(FB_IS_THRIFT(thft)); + priv = thft->priv; - if (!(thft->flags & FB_THRIFT_FLAG_COMPACT)) { - fb_thrift_write_byte(thft, bln != 0); + if ((priv->lastbool & 0x03) != 0x02) { + fb_thrift_write_byte(thft, value ? 0x01 : 0x02); return; } - if ((thft->lastbool & 0x03) != 0x02) { - fb_thrift_write_byte(thft, bln ? 0x01 : 0x02); - return; - } + pos = priv->lastbool >> 3; + priv->lastbool = 0; - pos = thft->lastbool >> 3; - thft->lastbool = 0; - - if ((pos >= thft->offset) && (pos < thft->bytes->len)) { - thft->bytes->data[pos] &= ~0x0F; - thft->bytes->data[pos] |= bln ? 0x01 : 0x02; + if ((pos >= priv->offset) && (pos < priv->bytes->len)) { + priv->bytes->data[pos] &= ~0x0F; + priv->bytes->data[pos] |= value ? 0x01 : 0x02; } } -/** - * Writes a single byte to a #fb_thrift. - * - * @param thft The #fb_thrift. - * @param byte The byte. - **/ -void fb_thrift_write_byte(fb_thrift_t *thft, guint8 byte) +void +fb_thrift_write_byte(FbThrift *thft, guint8 value) { - fb_thrift_write(thft, &byte, sizeof byte); + fb_thrift_write(thft, &value, sizeof value); } -/** - * Writes a double to a #fb_thrift. - * - * @param thft The #fb_thrift. - * @param dbl The double. - **/ -void fb_thrift_write_dbl(fb_thrift_t *thft, gdouble dbl) +void +fb_thrift_write_dbl(FbThrift *thft, gdouble value) { gint64 i64; /* Almost always 8, but check anyways */ - static const gsize size = MIN(sizeof dbl, sizeof i64); + static const gsize size = MIN(sizeof value, sizeof i64); - memcpy(&i64, &dbl, size); + memcpy(&i64, &value, size); fb_thrift_write_i64(thft, i64); } -/** - * Writes a 16-bit integer to a #fb_thrift. If the #fb_thrift is in - * compact mode, this will convert the integer to the zigzag format - * before writing. - * - * @param thft The #fb_thrift. - * @param i16 The integer. - **/ -void fb_thrift_write_i16(fb_thrift_t *thft, gint16 i16) +void +fb_thrift_write_i16(FbThrift *thft, gint16 value) { - g_return_if_fail(thft != NULL); - - if (!(thft->flags & FB_THRIFT_FLAG_COMPACT)) { - i16 = GINT16_TO_BE(i16); - fb_thrift_write(thft, &i16, sizeof i16); - return; - } - - fb_thrift_write_i32(thft, i16); + fb_thrift_write_i64(thft, value); } -/** - * Writes a 16-bit variable integer to a #fb_thrift. This function only - * writes if the #fb_thrift is in compact mode. This only writes the - * raw integer value without converting to the zigzag format. - * - * @param thft The #fb_thrift. - * @param u16 The integer. - **/ -void fb_thrift_write_vi16(fb_thrift_t *thft, guint16 u16) +void +fb_thrift_write_vi16(FbThrift *thft, guint16 value) { - fb_thrift_write_vi32(thft, u16); + fb_thrift_write_vi64(thft, value); } -/** - * Writes a 32-bit integer to a #fb_thrift. If the #fb_thrift is in - * compact mode, this will convert the integer to the zigzag format - * before writing. - * - * @param thft The #fb_thrift. - * @param i32 The integer. - **/ -void fb_thrift_write_i32(fb_thrift_t *thft, gint32 i32) +void +fb_thrift_write_i32(FbThrift *thft, gint32 value) { - g_return_if_fail(thft != NULL); - - if (!(thft->flags & FB_THRIFT_FLAG_COMPACT)) { - i32 = GINT32_TO_BE(i32); - fb_thrift_write(thft, &i32, sizeof i32); - return; - } - - i32 = (i32 << 1) ^ (i32 >> 31); - fb_thrift_write_vi64(thft, i32); + value = (value << 1) ^ (value >> 31); + fb_thrift_write_vi64(thft, value); } -/** - * Writes a 32-bit variable integer to a #fb_thrift. This function only - * writes if the #fb_thrift is in compact mode. This only writes the - * raw integer value without converting to the zigzag format. - * - * @param thft The #fb_thrift. - * @param u32 The integer. - **/ -void fb_thrift_write_vi32(fb_thrift_t *thft, guint32 u32) +void +fb_thrift_write_vi32(FbThrift *thft, guint32 value) { - fb_thrift_write_vi64(thft, u32); + fb_thrift_write_vi64(thft, value); } - -/** - * Writes a 64-bit integer to a #fb_thrift. If the #fb_thrift is in - * compact mode, this will convert the integer to the zigzag format - * before writing. - * - * @param thft The #fb_thrift. - * @param i64 The integer. - **/ -void fb_thrift_write_i64(fb_thrift_t *thft, gint64 i64) +void +fb_thrift_write_i64(FbThrift *thft, gint64 value) { - g_return_if_fail(thft != NULL); - - if (!(thft->flags & FB_THRIFT_FLAG_COMPACT)) { - i64 = GINT64_TO_BE(i64); - fb_thrift_write(thft, &i64, sizeof i64); - return; - } - - i64 = (i64 << 1) ^ (i64 >> 63); - fb_thrift_write_vi64(thft, i64); + value = (value << 1) ^ (value >> 63); + fb_thrift_write_vi64(thft, value); } -/** - * Writes a 64-bit variable integer to a #fb_thrift. This function only - * writes if the #fb_thrift is in compact mode. This only writes the - * raw integer value without converting to the zigzag format. - * - * @param thft The #fb_thrift. - * @param u64 The integer. - **/ -void fb_thrift_write_vi64(fb_thrift_t *thft, guint64 u64) +void +fb_thrift_write_vi64(FbThrift *thft, guint64 value) { gboolean last; - guint8 byte; - - g_return_if_fail(thft != NULL); - - if (!(thft->flags & FB_THRIFT_FLAG_COMPACT)) - return; + guint8 byte; do { - last = (u64 & ~0x7F) == 0; - byte = !last ? ((u64 & 0x7F) | 0x80) : (u64 & 0x0F); + last = (value & ~0x7F) == 0; + byte = value & 0x7F; + + if (!last) { + byte |= 0x80; + value >>= 7; + } fb_thrift_write_byte(thft, byte); - u64 >>= 7; } while (!last); } -/** - * Writes a string to a #fb_thrift. - * - * @param thft The #fb_thrift. - * @param str The string. - **/ -void fb_thrift_write_str(fb_thrift_t *thft, const gchar *str) +void +fb_thrift_write_str(FbThrift *thft, const gchar *value) { guint32 size; - g_return_if_fail(str != NULL); - - size = strlen(str); - - if (thft->flags & FB_THRIFT_FLAG_COMPACT) - fb_thrift_write_vi32(thft, size); - else - fb_thrift_write_i32(thft, size); + g_return_if_fail(value != NULL); - fb_thrift_write(thft, str, size); + size = strlen(value); + fb_thrift_write_vi32(thft, size); + fb_thrift_write(thft, value, size); } -/** - * Writes a field header to a #fb_thrift. - * - * @param thft The #fb_thrift. - * @param type The #fb_thrift_type. - * @param id The identifier. - **/ -void fb_thrift_write_field(fb_thrift_t *thft, fb_thrift_type_t type, - gint16 id) +void +fb_thrift_write_field(FbThrift *thft, FbThriftType type, gint16 id) { - gint16 iddf; + FbThriftPrivate *priv; + gint16 diff; - g_return_if_fail(thft != NULL); + g_return_if_fail(FB_IS_THRIFT(thft)); + priv = thft->priv; - if (!(thft->flags & FB_THRIFT_FLAG_COMPACT)) { - fb_thrift_write_byte(thft, type); - fb_thrift_write_i16(thft, id); - return; + if (type == FB_THRIFT_TYPE_BOOL) { + priv->lastbool = (priv->pos << 3) | 0x02; } - if (type == FB_THRIFT_TYPE_BOOL) - thft->lastbool = (thft->pos << 3) | 0x02; - type = fb_thrift_t2ct(type); - iddf = id - thft->lastid; + diff = id - priv->lastid; - if ((id <= thft->lastid) || (iddf > 15)) { + if ((id <= priv->lastid) || (diff > 0x0F)) { fb_thrift_write_byte(thft, type); fb_thrift_write_i16(thft, id); } else { - fb_thrift_write_byte(thft, (iddf << 4) | type); + fb_thrift_write_byte(thft, (diff << 4) | type); } - thft->lastid = id; + priv->lastid = id; } -/** - * Writes a field stop to a #fb_thrift. - * - * @param thft The #fb_thrift. - **/ -void fb_thrift_write_stop(fb_thrift_t *thft) +void +fb_thrift_write_stop(FbThrift *thft) { fb_thrift_write_byte(thft, FB_THRIFT_TYPE_STOP); } -/** - * Writes a list header to a #fb_thrift. - * - * @param thft The #fb_thrift. - * @param type The #fb_thrift_type. - * @param size The size. - **/ -void fb_thrift_write_list(fb_thrift_t *thft, fb_thrift_type_t type, - guint size) +void +fb_thrift_write_list(FbThrift *thft, FbThriftType type, guint size) { - if (!(thft->flags & FB_THRIFT_FLAG_COMPACT)) { - fb_thrift_write_byte(thft, type); - fb_thrift_write_i32(thft, size); - return; - } - type = fb_thrift_t2ct(type); if (size <= 14) { @@ -971,26 +630,10 @@ void fb_thrift_write_list(fb_thrift_t *thft, fb_thrift_type_t type, fb_thrift_write_byte(thft, 0xF0 | type); } -/** - * Writes a map header to a #fb_thrift. - * - * @param thft The #fb_thrift. - * @param ktype The key #fb_thrift_type. - * @param vtype The value #fb_thrift_type. - * @param size The size. - **/ -void fb_thrift_write_map(fb_thrift_t *thft, fb_thrift_type_t ktype, - fb_thrift_type_t vtype, guint size) +void +fb_thrift_write_map(FbThrift *thft, FbThriftType ktype, FbThriftType vtype, + guint size) { - g_return_if_fail(thft != NULL); - - if (!(thft->flags & FB_THRIFT_FLAG_COMPACT)) { - fb_thrift_write_byte(thft, ktype); - fb_thrift_write_byte(thft, vtype); - fb_thrift_write_i32(thft, size); - return; - } - if (size == 0) { fb_thrift_write_byte(thft, 0); return; @@ -1003,27 +646,14 @@ void fb_thrift_write_map(fb_thrift_t *thft, fb_thrift_type_t ktype, fb_thrift_write_byte(thft, (ktype << 4) | vtype); } -/** - * Writes a set header to a #fb_thrift. - * - * @param thft The #fb_thrift. - * @param type The #fb_thrift_type. - * @param size The size. - **/ -void fb_thrift_write_set(fb_thrift_t *thft, fb_thrift_type_t type, - guint size) +void +fb_thrift_write_set(FbThrift *thft, FbThriftType type, guint size) { fb_thrift_write_list(thft, type, size); } -/** - * Converts a #fb_thrift_type to a compact type. - * - * @param type The #fb_thrift_type. - * - * @return The equivalent compact type. - **/ -guint8 fb_thrift_t2ct(fb_thrift_type_t type) +guint8 +fb_thrift_t2ct(FbThriftType type) { static const guint8 types[] = { [FB_THRIFT_TYPE_STOP] = 0, @@ -1044,20 +674,12 @@ guint8 fb_thrift_t2ct(fb_thrift_type_t type) [FB_THRIFT_TYPE_LIST] = 9 }; - if (G_UNLIKELY(type >= G_N_ELEMENTS(types))) - return 0; - + g_return_val_if_fail(type < G_N_ELEMENTS(types), 0); return types[type]; } -/** - * Converts a compact type to a #fb_thrift_type. - * - * @param type The compact type. - * - * @return The equivalent #fb_thrift_type. - **/ -fb_thrift_type_t fb_thrift_ct2t(guint8 type) +FbThriftType +fb_thrift_ct2t(guint8 type) { static const guint8 types[] = { [0] = FB_THRIFT_TYPE_STOP, @@ -1075,9 +697,6 @@ fb_thrift_type_t fb_thrift_ct2t(guint8 type) [12] = FB_THRIFT_TYPE_STRUCT }; - if (G_UNLIKELY(type >= G_N_ELEMENTS(types))) - return 0; - + g_return_val_if_fail(type < G_N_ELEMENTS(types), 0); return types[type]; - } diff --git a/facebook/facebook-thrift.h b/facebook/facebook-thrift.h index 0438e7b..25b43f5 100644 --- a/facebook/facebook-thrift.h +++ b/facebook/facebook-thrift.h @@ -15,155 +15,582 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -/** @file **/ +#ifndef _FACEBOOK_THRIFT_H_ +#define _FACEBOOK_THRIFT_H_ -#ifndef _FACEBOOK_THRIFT_H -#define _FACEBOOK_THRIFT_H +/** + * SECTION:thrift + * @section_id: facebook-thrift + * @short_description: <filename>facebook-thrift.h</filename> + * @title: Thrift Reader/Writer + * + * The Thrift reader/writer. + */ #include <glib.h> +#include <glib-object.h> +#define FB_TYPE_THRIFT (fb_thrift_get_type()) +#define FB_THRIFT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), FB_TYPE_THRIFT, FbThrift)) +#define FB_THRIFT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), FB_TYPE_THRIFT, FbThriftClass)) +#define FB_IS_THRIFT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), FB_TYPE_THRIFT)) +#define FB_IS_THRIFT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), FB_TYPE_THRIFT)) +#define FB_THRIFT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), FB_TYPE_THRIFT, FbThriftClass)) -/** The flags of a #fb_thrift. **/ -typedef enum fb_thrift_flags fb_thrift_flags_t; - -/** The #fb_thrift data types. **/ -typedef enum fb_thrift_type fb_thrift_type_t; - -/** The main structure for Thrift IO. **/ -typedef struct fb_thrift fb_thrift_t; - +typedef struct _FbThrift FbThrift; +typedef struct _FbThriftClass FbThriftClass; +typedef struct _FbThriftPrivate FbThriftPrivate; /** - * The flags of a #fb_thrift. - **/ -enum fb_thrift_flags + * FbThriftType: + * @FB_THRIFT_TYPE_STOP: A stopper for certain types. + * @FB_THRIFT_TYPE_VOID: A void or empty value. + * @FB_THRIFT_TYPE_BOOL: A boolean (#TRUE or #FALSE). + * @FB_THRIFT_TYPE_BYTE: A signed 8-bit integer. + * @FB_THRIFT_TYPE_DOUBLE: A 64-bit floating point number. + * @FB_THRIFT_TYPE_I16: A signed 16-bit integer. + * @FB_THRIFT_TYPE_I32: A signed 32-bit integer. + * @FB_THRIFT_TYPE_I64: A signed 64-bit integer. + * @FB_THRIFT_TYPE_STRING: A UTF-8 encoded string. + * @FB_THRIFT_TYPE_STRUCT: A set of typed fields. + * @FB_THRIFT_TYPE_MAP: A map of unique keys to values. + * @FB_THRIFT_TYPE_SET: A unique set of values. + * @FB_THRIFT_TYPE_LIST: A ordered list of values. + * @FB_THRIFT_TYPE_ENUM: A 32-bit enumerated list. + * @FB_THRIFT_TYPE_UNKNOWN: An unknown type. + * + * The Thrift data types. + */ +typedef enum { - FB_THRIFT_FLAG_COMPACT = 1 << 0, /** Compact types. **/ - FB_THRIFT_FLAG_INTERNAL = 1 << 1 /** Internal #GByteArray. **/ -}; + FB_THRIFT_TYPE_STOP = 0, + FB_THRIFT_TYPE_VOID = 1, + FB_THRIFT_TYPE_BOOL = 2, + FB_THRIFT_TYPE_BYTE = 3, + FB_THRIFT_TYPE_DOUBLE = 4, + FB_THRIFT_TYPE_I16 = 6, + FB_THRIFT_TYPE_I32 = 8, + FB_THRIFT_TYPE_I64 = 10, + FB_THRIFT_TYPE_STRING = 11, + FB_THRIFT_TYPE_STRUCT = 12, + FB_THRIFT_TYPE_MAP = 13, + FB_THRIFT_TYPE_SET = 14, + FB_THRIFT_TYPE_LIST = 15, + FB_THRIFT_TYPE_ENUM = 16, + + FB_THRIFT_TYPE_UNKNOWN +} FbThriftType; /** - * The #fb_thrift data types. - **/ -enum fb_thrift_type + * FbThrift: + * + * Represents a reader/writer for compact Thrift data. + */ +struct _FbThrift { - FB_THRIFT_TYPE_STOP = 0, /** Stop. **/ - FB_THRIFT_TYPE_VOID = 1, /** Void. **/ - FB_THRIFT_TYPE_BOOL = 2, /** Boolean. **/ - FB_THRIFT_TYPE_BYTE = 3, /** Byte. **/ - FB_THRIFT_TYPE_DOUBLE = 4, /** Double. **/ - FB_THRIFT_TYPE_I16 = 6, /** Integer (16-bit). **/ - FB_THRIFT_TYPE_I32 = 8, /** Integer (32-bit). **/ - FB_THRIFT_TYPE_I64 = 10, /** Integer (64-bit). **/ - FB_THRIFT_TYPE_STRING = 11, /** String. **/ - FB_THRIFT_TYPE_STRUCT = 12, /** Structure. **/ - FB_THRIFT_TYPE_MAP = 13, /** Map. **/ - FB_THRIFT_TYPE_SET = 14, /** Set. **/ - FB_THRIFT_TYPE_LIST = 15, /** List. **/ - FB_THRIFT_TYPE_ENUM = 16, /** Enumerator. **/ - - FB_THRIFT_TYPE_UNKNOWN /** Unknown. **/ - + /*< private >*/ + GObject parent; + FbThriftPrivate *priv; }; /** - * The main structure for Thrift IO. - **/ -struct fb_thrift + * FbThriftClass: + * + * The base class for all #FbThrift's. + */ +struct _FbThriftClass { - fb_thrift_flags_t flags; /** The #fb_thrift_flags. **/ + /*< private >*/ + GObjectClass parent_class; +}; - gint16 lastid; /** The last identifier. **/ - guint lastbool; /** The last boolean value. **/ +/** + * fb_thrift_get_type: + * + * Returns: The #GType for an #FbThrift. + */ +GType +fb_thrift_get_type(void); - GByteArray *bytes; /** The #GByteArray of data. **/ - guint offset; /** The data offset. **/ - guint pos; /** The cursor position. **/ -}; +/** + * fb_thrift_new: + * @bytes: The #GByteArray to read or write. + * @offset: The offset in bytes of the data in @bytes. + * + * Creates a new #FbThrift. The returned #FbThrift should be freed with + * #g_object_unref() when no longer needed. This will optionally use a + * #GByteArray at an offset, rather than a newly created and internal + * #GByteArray. + * + * Returns: The new #FbThrift. + */ +FbThrift * +fb_thrift_new(GByteArray *bytes, guint offset); +/** + * fb_thrift_get_bytes: + * @thft: The #FbThrift. + * + * Gets the underlying #GByteArray of an #FbThrift. + * + * Returns: The #GByteArray. + */ +const GByteArray * +fb_thrift_get_bytes(FbThrift *thft); -fb_thrift_t *fb_thrift_new(GByteArray *bytes, guint offset, gboolean compact); +/** + * fb_thrift_get_pos: + * @thft: The #FbThrift. + * + * Gets the cursor position of an #FbThrift. + * + * Returns: The cursor position. + */ +guint +fb_thrift_get_pos(FbThrift *thft); -void fb_thrift_free(fb_thrift_t *thft); +/** + * fb_thrift_set_pos: + * @thft: The #FbThrift. + * @pos: The position. + * + * Sets the cursor position of an #FbThrift. + * + * Returns: The #GByteArray. + */ +void +fb_thrift_set_pos(FbThrift *thft, guint pos); -void fb_thrift_reset(fb_thrift_t *thft); +/** + * fb_thrift_reset: + * @thft: The #FbThrift. + * + * Resets the cursor position of an #FbThrift. + * + * Returns: The #GByteArray. + */ +void +fb_thrift_reset(FbThrift *thft); -gboolean fb_thrift_read(fb_thrift_t *thft, gpointer data, guint size); +/** + * fb_thrift_read: + * @thft: The #FbThrift. + * @data: The data buffer. + * @size: The size of @buffer. + * + * Reads data from the #FbThrift into a buffer. If @data is #NULL, this + * will simply advance the cursor position. + * + * Returns: #TRUE if the data was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read(FbThrift *thft, gpointer data, guint size); -gboolean fb_thrift_read_bool(fb_thrift_t *thft, gboolean *bln); +/** + * fb_thrift_read_bool: + * @thft: The #FbThrift. + * @value: The return location for the value, or #NULL. + * + * Reads a boolean value from the #FbThrift. If @value is #NULL, this + * will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_bool(FbThrift *thft, gboolean *value); -gboolean fb_thrift_read_byte(fb_thrift_t *thft, guint8 *byte); +/** + * fb_thrift_read_byte: + * @thft: The #FbThrift. + * @value: The return location for the value, or #NULL. + * + * Reads an 8-bit integer value from the #FbThrift. If @value is #NULL, + * this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_byte(FbThrift *thft, guint8 *value); -gboolean fb_thrift_read_dbl(fb_thrift_t *thft, gdouble *dbl); +/** + * fb_thrift_read_dbl: + * @thft: The #FbThrift. + * @value: The return location for the value, or #NULL. + * + * Reads a 64-bit floating point value from the #FbThrift. If @value + * is #NULL, this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_dbl(FbThrift *thft, gdouble *value); -gboolean fb_thrift_read_i16(fb_thrift_t *thft, gint16 *i16); +/** + * fb_thrift_read_i16: + * @thft: The #FbThrift. + * @value: The return location for the value, or #NULL. + * + * Reads a signed 16-bit integer value from the #FbThrift. This will + * convert the integer from the zig-zag format. If @value is #NULL, + * this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_i16(FbThrift *thft, gint16 *value); -gboolean fb_thrift_read_vi16(fb_thrift_t *thft, guint16 *u16); +/** + * fb_thrift_read_vi16: + * @thft: The #FbThrift. + * @value: The return location for the value, or #NULL. + * + * Reads a 16-bit integer value from the #FbThrift. This reads the raw + * integer value without converting it from the zig-zag format. If + * @value is #NULL, this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_vi16(FbThrift *thft, guint16 *value); -gboolean fb_thrift_read_i32(fb_thrift_t *thft, gint32 *i32); +/** + * fb_thrift_read_i32: + * @thft: The #FbThrift. + * @value: The return location for the value, or #NULL. + * + * Reads a signed 32-bit integer value from the #FbThrift. This will + * convert the integer from the zig-zag format. If @value is #NULL, + * this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_i32(FbThrift *thft, gint32 *value); -gboolean fb_thrift_read_vi32(fb_thrift_t *thft, guint32 *u32); +/** + * fb_thrift_read_vi32: + * @thft: The #FbThrift. + * @value: The return location for the value, or #NULL. + * + * Reads a 32-bit integer value from the #FbThrift. This reads the raw + * integer value without converting it from the zig-zag format. If + * @value is #NULL, this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_vi32(FbThrift *thft, guint32 *value); -gboolean fb_thrift_read_i64(fb_thrift_t *thft, gint64 *i64); +/** + * fb_thrift_read_i64: + * @thft: The #FbThrift. + * @value: The return location for the value, or #NULL. + * + * Reads a signed 64-bit integer value from the #FbThrift. This will + * convert the integer from the zig-zag format. If @value is #NULL, + * this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_i64(FbThrift *thft, gint64 *value); -gboolean fb_thrift_read_vi64(fb_thrift_t *thft, guint64 *u64); +/** + * fb_thrift_read_vi64: + * @thft: The #FbThrift. + * @value: The return location for the value, or #NULL. + * + * Reads a 64-bit integer value from the #FbThrift. This reads the raw + * integer value without converting it from the zig-zag format. If + * @value is #NULL, this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_vi64(FbThrift *thft, guint64 *value); -gboolean fb_thrift_read_str(fb_thrift_t *thft, gchar **str); +/** + * fb_thrift_read_str: + * @thft: The #FbThrift. + * @value: The return location for the value, or #NULL. + * + * Reads a string value from the #FbThrift. The value returned to + * @value should be freed with #g_free() when no longer needed. If + * @value is #NULL, this will simply advance the cursor position. + * + * Returns: #TRUE if the value was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_str(FbThrift *thft, gchar **value); -gboolean fb_thrift_read_field(fb_thrift_t *thft, fb_thrift_type_t *type, - gint16 *id); +/** + * fb_thrift_read_field: + * @thft: The #FbThrift. + * @type: The return location for the #FbThriftType. + * @id: The return location for the identifier, or #NULL. + * + * Reads a field header from the #FbThrift. + * + * Returns: #TRUE if the field header was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_field(FbThrift *thft, FbThriftType *type, gint16 *id); -gboolean fb_thrift_read_stop(fb_thrift_t *thft); +/** + * fb_thrift_read_stop: + * @thft: The #FbThrift. + * + * Reads a field stop from the #FbThrift. + * + * Returns: #TRUE if the field stop was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_stop(FbThrift *thft); -gboolean fb_thrift_read_isstop(fb_thrift_t *thft); +/** + * fb_thrift_read_isstop: + * @thft: The #FbThrift. + * + * Determines if the next byte of the #FbThrift is a field stop. + * + * Returns: #TRUE if the next byte is a field stop, otherwise #FALSE. + */ +gboolean +fb_thrift_read_isstop(FbThrift *thft); -gboolean fb_thrift_read_list(fb_thrift_t *thft, fb_thrift_type_t *type, - guint *size); +/** + * fb_thrift_read_list: + * @thft: The #FbThrift. + * @type: The return location for the #FbThriftType. + * @size: The return location for the size. + * + * Reads a list header from the #FbThrift. + * + * Returns: #TRUE if the list header was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_list(FbThrift *thft, FbThriftType *type, guint *size); -gboolean fb_thrift_read_map(fb_thrift_t *thft, fb_thrift_type_t *ktype, - fb_thrift_type_t *vtype, guint *size); +/** + * fb_thrift_read_map: + * @thft: The #FbThrift. + * @ktype: The return location for the key #FbThriftType. + * @vtype: The return location for the value #FbThriftType. + * @size: The return location for the size. + * + * Reads a map header from the #FbThrift. + * + * Returns: #TRUE if the map header was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_map(FbThrift *thft, FbThriftType *ktype, FbThriftType *vtype, + guint *size); -gboolean fb_thrift_read_set(fb_thrift_t *thft, fb_thrift_type_t *type, - guint *size); +/** + * fb_thrift_read_set: + * @thft: The #FbThrift. + * @type: The return location for the #FbThriftType. + * @size: The return location for the size. + * + * Reads a set header from the #FbThrift. + * + * Returns: #TRUE if the set header was read, otherwise #FALSE. + */ +gboolean +fb_thrift_read_set(FbThrift *thft, FbThriftType *type, guint *size); -void fb_thrift_write(fb_thrift_t *thft, gconstpointer data, guint size); +/** + * fb_thrift_write: + * @thft: The #FbThrift. + * @data: The data buffer. + * @size: The size of @buffer. + * + * Writes data to the #FbThrift. + */ +void +fb_thrift_write(FbThrift *thft, gconstpointer data, guint size); -void fb_thrift_write_bool(fb_thrift_t *thft, gboolean bln); +/** + * fb_thrift_write_bool: + * @thft: The #FbThrift. + * @value: The value. + * + * Writes a boolean value to the #FbThrift. + */ +void +fb_thrift_write_bool(FbThrift *thft, gboolean value); -void fb_thrift_write_byte(fb_thrift_t *thft, guint8 byte); +/** + * fb_thrift_write_byte: + * @thft: The #FbThrift. + * @value: The value. + * + * Writes an 8-bit integer value to the #FbThrift. + */ +void +fb_thrift_write_byte(FbThrift *thft, guint8 value); -void fb_thrift_write_dbl(fb_thrift_t *thft, gdouble dbl); +/** + * fb_thrift_write_dbl: + * @thft: The #FbThrift. + * @value: The value. + * + * Writes a 64-bit floating point value to the #FbThrift. + */ +void +fb_thrift_write_dbl(FbThrift *thft, gdouble value); -void fb_thrift_write_i16(fb_thrift_t *thft, gint16 i16); +/** + * fb_thrift_write_i16: + * @thft: The #FbThrift. + * @value: The value. + * + * Writes a signed 16-bit integer value to the #FbThrift. This will + * convert the integer to the zig-zag format. + */ +void +fb_thrift_write_i16(FbThrift *thft, gint16 value); -void fb_thrift_write_vi16(fb_thrift_t *thft, guint16 u16); +/** + * fb_thrift_write_vi16: + * @thft: The #FbThrift. + * @value: The value. + * + * Writes a 16-bit integer value to the #FbThrift. This writes the raw + * integer value without converting it to the zig-zag format. + */ +void +fb_thrift_write_vi16(FbThrift *thft, guint16 value); -void fb_thrift_write_i32(fb_thrift_t *thft, gint32 i32); +/** + * fb_thrift_write_i32: + * @thft: The #FbThrift. + * @value: The value. + * + * Writes a signed 32-bit integer value to the #FbThrift. This will + * convert the integer to the zig-zag format. + */ +void +fb_thrift_write_i32(FbThrift *thft, gint32 value); -void fb_thrift_write_vi32(fb_thrift_t *thft, guint32 u32); +/** + * fb_thrift_write_vi32: + * @thft: The #FbThrift. + * @value: The value. + * + * Writes a 32-bit integer value to the #FbThrift. This writes the raw + * integer value without converting it to the zig-zag format. + */ +void +fb_thrift_write_vi32(FbThrift *thft, guint32 value); -void fb_thrift_write_i64(fb_thrift_t *thft, gint64 i64); +/** + * fb_thrift_write_i64: + * @thft: The #FbThrift. + * @value: The value. + * + * Writes a signed 64-bit integer value to the #FbThrift. This will + * convert the integer to the zig-zag format. + */ +void +fb_thrift_write_i64(FbThrift *thft, gint64 value); -void fb_thrift_write_vi64(fb_thrift_t *thft, guint64 u64); +/** + * fb_thrift_write_vi64: + * @thft: The #FbThrift. + * @value: The value. + * + * Writes a 64-bit integer value to the #FbThrift. This writes the raw + * integer value without converting it to the zig-zag format. + */ +void +fb_thrift_write_vi64(FbThrift *thft, guint64 value); -void fb_thrift_write_str(fb_thrift_t *thft, const gchar *str); +/** + * fb_thrift_write_str: + * @thft: The #FbThrift. + * @value: The value. + * + * Writes a string value to the #FbThrift. + */ +void +fb_thrift_write_str(FbThrift *thft, const gchar *value); -void fb_thrift_write_field(fb_thrift_t *thft, fb_thrift_type_t type, - gint16 id); +/** + * fb_thrift_write_field: + * @thft: The #FbThrift. + * @type: The #FbThriftType. + * @id: The identifier. + * + * Writes a field header to the #FbThrift. + */ +void +fb_thrift_write_field(FbThrift *thft, FbThriftType type, gint16 id); -void fb_thrift_write_stop(fb_thrift_t *thft); +/** + * fb_thrift_write_stop: + * @thft: The #FbThrift. + * + * Writes a field stop to the #FbThrift. + */ +void +fb_thrift_write_stop(FbThrift *thft); -void fb_thrift_write_list(fb_thrift_t *thft, fb_thrift_type_t type, - guint size); +/** + * fb_thrift_write_list: + * @thft: The #FbThrift. + * @type: The #FbThriftType. + * @size: The size. + * + * Writes a list header to the #FbThrift. + */ +void +fb_thrift_write_list(FbThrift *thft, FbThriftType type, guint size); -void fb_thrift_write_map(fb_thrift_t *thft, fb_thrift_type_t ktype, - fb_thrift_type_t vtype, guint size); +/** + * fb_thrift_write_map: + * @thft: The #FbThrift. + * @ktype: The key #FbThriftType. + * @vtype: The value #FbThriftType. + * @size: The size. + * + * Writes a map header to the #FbThrift. + */ +void +fb_thrift_write_map(FbThrift *thft, FbThriftType ktype, FbThriftType vtype, + guint size); -void fb_thrift_write_set(fb_thrift_t *thft, fb_thrift_type_t type, - guint size); +/** + * fb_thrift_write_set: + * @thft: The #FbThrift. + * @type: The #FbThriftType. + * @size: The size. + * + * Writes a set header to the #FbThrift. + */ +void +fb_thrift_write_set(FbThrift *thft, FbThriftType type, guint size); -guint8 fb_thrift_t2ct(fb_thrift_type_t type); +/** + * fb_thrift_t2ct: + * @type: The #FbThriftType. + * + * Converts a #FbThriftType to a compact type. + * + * Return: The equivalent compact type. + */ +guint8 +fb_thrift_t2ct(FbThriftType type); -fb_thrift_type_t fb_thrift_ct2t(guint8 type); +/** + * fb_thrift_ct2t: + * @type: The compact type. + * + * Converts a compact type to an #FbThriftType. + * + * Return: The equivalent #FbThriftType. + */ +FbThriftType +fb_thrift_ct2t(guint8 type); -#endif /* _FACEBOOK_THRIFT_H */ +#endif /* _FACEBOOK_THRIFT_H_ */ diff --git a/facebook/facebook-util.c b/facebook/facebook-util.c index 4b959ac..dba7b82 100644 --- a/facebook/facebook-util.c +++ b/facebook/facebook-util.c @@ -15,80 +15,170 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <bitlbee.h> +#include <sha1.h> #include <stdarg.h> #include <string.h> #include <zlib.h> #include "facebook-util.h" -/** - * Determines the debugging state of the plugin. - * - * @return TRUE if debugging is enabled, otherwise FALSE. - **/ -#ifdef DEBUG_FACEBOOK -gboolean fb_util_debugging(void) +GQuark +fb_util_error_quark(void) +{ + static GQuark q = 0; + + if (G_UNLIKELY(q == 0)) { + q = g_quark_from_static_string("fb-util-error-quark"); + } + + return q; +} + +void +fb_util_debug(FbDebugLevel level, const gchar *format, ...) +{ + va_list ap; + + va_start(ap, format); + fb_util_vdebug(level, format, ap); + va_end(ap); +} + +void +fb_util_vdebug(FbDebugLevel level, const gchar *format, va_list ap) { + const gchar *lstr; + gchar *str; + static gboolean debug = FALSE; static gboolean setup = FALSE; + g_return_if_fail(format != NULL); + if (G_UNLIKELY(!setup)) { - debug = g_getenv("BITLBEE_DEBUG") || - g_getenv("BITLBEE_DEBUG_FACEBOOK"); + debug = (g_getenv("BITLBEE_DEBUG") != NULL) || + (g_getenv("BITLBEE_DEBUG_FACEBOOK") != NULL); setup = TRUE; } - return debug; + if (!debug) { + return; + } + + switch (level) { + case FB_UTIL_DEBUG_LEVEL_MISC: + lstr = "MISC"; + break; + case FB_UTIL_DEBUG_LEVEL_INFO: + lstr = "INFO"; + break; + case FB_UTIL_DEBUG_LEVEL_WARN: + lstr = "WARN"; + break; + case FB_UTIL_DEBUG_LEVEL_ERROR: + lstr = "ERROR"; + break; + case FB_UTIL_DEBUG_LEVEL_FATAL: + lstr = "FATAL"; + break; + + default: + g_return_if_reached(); + return; + } + + str = g_strdup_vprintf(format, ap); + g_print("[%s] %s: %s\n", lstr, "facebook", str); + g_free(str); } -#endif /* DEBUG_FACEBOOK */ -/** - * Dumps a #GByteArray to the debugging stream. This formats the output - * similar to that of `hexdump -C`. - * - * @param bytes The #GByteArray. - * @param indent The indent width. - * @param fmt The format string or NULL. - * @param ... The format arguments. - **/ -#ifdef DEBUG_FACEBOOK -void fb_util_hexdump(const GByteArray *bytes, guint indent, - const gchar *fmt, ...) +void +fb_util_debug_misc(const gchar *format, ...) +{ + va_list ap; + + va_start(ap, format); + fb_util_vdebug(FB_UTIL_DEBUG_LEVEL_MISC, format, ap); + va_end(ap); +} + +void +fb_util_debug_info(const gchar *format, ...) +{ + va_list ap; + + va_start(ap, format); + fb_util_vdebug(FB_UTIL_DEBUG_LEVEL_INFO, format, ap); + va_end(ap); +} + +void +fb_util_debug_warn(const gchar *format, ...) +{ + va_list ap; + + va_start(ap, format); + fb_util_vdebug(FB_UTIL_DEBUG_LEVEL_WARN, format, ap); + va_end(ap); +} + +void +fb_util_debug_error(const gchar *format, ...) +{ + va_list ap; + + va_start(ap, format); + fb_util_vdebug(FB_UTIL_DEBUG_LEVEL_ERROR, format, ap); + va_end(ap); +} + +void +fb_util_debug_fatal(const gchar *format, ...) { + va_list ap; + + va_start(ap, format); + fb_util_vdebug(FB_UTIL_DEBUG_LEVEL_FATAL, format, ap); + va_end(ap); +} + +void +fb_util_debug_hexdump(FbDebugLevel level, const GByteArray *bytes, + const gchar *format, ...) +{ + gchar c; + guint i; + guint j; GString *gstr; - va_list ap; - gchar *instr; - guint i; - guint j; - gchar c; - - if (fmt != NULL) { - va_start(ap, fmt); - instr = g_strdup_vprintf(fmt, ap); - FB_UTIL_DEBUGLN("%s", instr); - g_free(instr); + va_list ap; + + static const gchar *indent = " "; + + g_return_if_fail(bytes != NULL); + + if (format != NULL) { + va_start(ap, format); + fb_util_vdebug(level, format, ap); va_end(ap); } - instr = g_strnfill(indent, ' '); - gstr = g_string_sized_new(80); - i = 0; + gstr = g_string_sized_new(80); - if (G_UNLIKELY(bytes == NULL)) - goto finish; - - for (; i < bytes->len; i += 16) { - g_string_append_printf(gstr, "%s%08x ", instr, i); + for (i = 0; i < bytes->len; i += 16) { + g_string_append_printf(gstr, "%s%08x ", indent, i); for (j = 0; j < 16; j++) { if ((i + j) < bytes->len) { - g_string_append_printf(gstr, "%02x ", bytes->data[i + j]); + g_string_append_printf(gstr, "%02x ", + bytes->data[i + j]); } else { g_string_append(gstr, " "); } - if (j == 7) + if (j == 7) { g_string_append_c(gstr, ' '); + } } g_string_append(gstr, " |"); @@ -96,81 +186,138 @@ void fb_util_hexdump(const GByteArray *bytes, guint indent, for (j = 0; (j < 16) && ((i + j) < bytes->len); j++) { c = bytes->data[i + j]; - if (!g_ascii_isprint(c) || g_ascii_isspace(c)) + if (!g_ascii_isprint(c) || g_ascii_isspace(c)) { c = '.'; + } g_string_append_c(gstr, c); } g_string_append_c(gstr, '|'); - FB_UTIL_DEBUGLN("%s", gstr->str); + fb_util_debug(level, "%s", gstr->str); g_string_erase(gstr, 0, -1); } -finish: - g_string_append_printf(gstr, "%s%08x", instr, i); - FB_UTIL_DEBUGLN("%s", gstr->str); - + g_string_append_printf(gstr, "%s%08x", indent, i); + fb_util_debug(level, "%s", gstr->str); g_string_free(gstr, TRUE); - g_free(instr); } -#endif /* DEBUG_FACEBOOK */ -/** - * Compare two strings case insensitively. This is useful for where - * the return value must be a boolean, such as with a #GEqualFunc. - * - * @param s1 The first string. - * @param s2 The second string. - * - * @return TRUE if the strings are equal, otherwise FALSE. - **/ -gboolean fb_util_str_iequal(const gchar *s1, const gchar *s2) +gchar * +fb_util_locale_str(void) { - return g_ascii_strcasecmp(s1, s2) == 0; + const gchar * const *langs; + const gchar *lang; + gchar *chr; + guint i; + + static const gchar chrs[] = {'.', '@'}; + + langs = g_get_language_names(); + lang = langs[0]; + + if (g_strcmp0(lang, "C") == 0) { + return g_strdup("en_US"); + } + + for (i = 0; i < G_N_ELEMENTS(chrs); i++) { + chr = strchr(lang, chrs[i]); + + if (chr != NULL) { + return g_strndup(lang, chr - lang); + } + } + + return g_strdup(lang); } -/** - * Implemented #alloc_func for #g_malloc(). - * - * @param opaque The user-defined data, which is NULL. - * @param items The number of items. - * @param size The size of each item. - * - * @return The pointer to the allocated memory. - **/ -static voidpf fb_util_zalloc(voidpf opaque, uInt items, uInt size) +gchar * +fb_util_randstr(gsize size) +{ + gchar *ret; + GRand *rand; + guint i; + guint j; + + static const gchar chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789"; + static const gsize charc = G_N_ELEMENTS(chars) - 1; + + if (G_UNLIKELY(size < 1)) { + return NULL; + } + + rand = g_rand_new(); + ret = g_new(gchar, size + 1); + + for (i = 0; i < size; i++) { + j = g_rand_int_range(rand, 0, charc); + ret[i] = chars[j]; + } + + ret[size] = 0; + g_rand_free(rand); + return ret; +} + +gboolean +fb_util_str_is(const gchar *str, GAsciiType type) +{ + gsize i; + gsize size; + guchar c; + + g_return_val_if_fail(str != NULL, FALSE); + size = strlen(str); + + for (i = 0; i < size; i++) { + c = (guchar) str[i]; + + if ((g_ascii_table[c] & type) == 0) { + return FALSE; + } + } + + return TRUE; +} + +gchar * +fb_util_uuid(void) +{ + guint8 buf[50]; + sha1_state_t sha; + + sha1_init(&sha); + random_bytes(buf, sizeof buf); + sha1_append(&sha, buf, sizeof buf); + return sha1_random_uuid(&sha); +} + +static voidpf +fb_util_zalloc(voidpf opaque, uInt items, uInt size) { return g_malloc(size * items); } -/** - * Implemented #free_func for #g_free(). - * - * @param opaque The user-defined data, which is NULL. - * @param address The pointer address. - **/ -static void fb_util_zfree(voidpf opaque, voidpf address) +static void +fb_util_zfree(voidpf opaque, voidpf address) { g_free(address); } -/** - * Determines if a #GByteArray is zlib compressed. - * - * @param bytes The #GByteArray. - * - * @return TRUE if the #GByteArray is compressed, otherwise FALSE. - **/ -gboolean fb_util_zcompressed(const GByteArray *bytes) +gboolean +fb_util_zcompressed(const GByteArray *bytes) { guint8 b0; guint8 b1; g_return_val_if_fail(bytes != NULL, FALSE); - if (bytes->len < 2) + if (bytes->len < 2) { return FALSE; + } b0 = *(bytes->data + 0); b1 = *(bytes->data + 1); @@ -179,38 +326,32 @@ gboolean fb_util_zcompressed(const GByteArray *bytes) ((b0 & 0x0F) == Z_DEFLATED); /* Check the method */ } -/** - * Compresses a #GByteArray with zlib. The returned #GByteArray should - * be freed with #g_byte_array_free() when no longer needed. - * - * @param bytes The #GByteArray. - * - * @return The resulting #GByteArray, or NULL on error. - **/ -GByteArray *fb_util_zcompress(const GByteArray *bytes) +GByteArray * +fb_util_zcompress(const GByteArray *bytes) { GByteArray *ret; - z_stream zs; - gsize size; - gint res; + gint res; + gsize size; + z_stream zs; g_return_val_if_fail(bytes != NULL, NULL); memset(&zs, 0, sizeof zs); - zs.zalloc = fb_util_zalloc; - zs.zfree = fb_util_zfree; - zs.next_in = bytes->data; + zs.zalloc = fb_util_zalloc; + zs.zfree = fb_util_zfree; + zs.next_in = bytes->data; zs.avail_in = bytes->len; - if (deflateInit(&zs, Z_BEST_COMPRESSION) != Z_OK) + if (deflateInit(&zs, Z_BEST_COMPRESSION) != Z_OK) { return NULL; + } size = compressBound(bytes->len); - ret = g_byte_array_new(); + ret = g_byte_array_new(); g_byte_array_set_size(ret, size); - zs.next_out = ret->data; + zs.next_out = ret->data; zs.avail_out = size; res = deflate(&zs, Z_FINISH); @@ -228,36 +369,30 @@ GByteArray *fb_util_zcompress(const GByteArray *bytes) return ret; } -/** - * Uncompresses a zlib compressed #GByteArray. The returned #GByteArray - * should be freed with #g_byte_array_free() when no longer needed. - * - * @param bytes The #GByteArray. - * - * @return The resulting #GByteArray, or NULL on error. - **/ -GByteArray *fb_util_zuncompress(const GByteArray *bytes) +GByteArray * +fb_util_zuncompress(const GByteArray *bytes) { GByteArray *ret; - z_stream zs; - guint8 out[1024]; - gint res; + gint res; + guint8 out[1024]; + z_stream zs; g_return_val_if_fail(bytes != NULL, NULL); memset(&zs, 0, sizeof zs); - zs.zalloc = fb_util_zalloc; - zs.zfree = fb_util_zfree; - zs.next_in = bytes->data; + zs.zalloc = fb_util_zalloc; + zs.zfree = fb_util_zfree; + zs.next_in = bytes->data; zs.avail_in = bytes->len; - if (inflateInit(&zs) != Z_OK) + if (inflateInit(&zs) != Z_OK) { return NULL; + } ret = g_byte_array_new(); do { - zs.next_out = out; + zs.next_out = out; zs.avail_out = sizeof out; res = inflate(&zs, Z_NO_FLUSH); diff --git a/facebook/facebook-util.h b/facebook/facebook-util.h index cf29878..f1eb84a 100644 --- a/facebook/facebook-util.h +++ b/facebook/facebook-util.h @@ -15,49 +15,234 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -/** @file **/ +#ifndef _FACEBOOK_UTIL_H_ +#define _FACEBOOK_UTIL_H_ -#ifndef _FACEBOOK_UTIL_H -#define _FACEBOOK_UTIL_H +/** + * SECTION:util + * @section_id: facebook-util + * @short_description: <filename>facebook-util.h</filename> + * @title: General Utilities + * + * The general utilities. + */ #include <glib.h> +#include <glib-object.h> + +/** + * FB_UTIL_ERROR: + * + * The #GQuark of the domain of utility errors. + */ +#define FB_UTIL_ERROR fb_util_error_quark() + +typedef enum +{ + FB_UTIL_DEBUG_LEVEL_MISC, + FB_UTIL_DEBUG_LEVEL_INFO, + FB_UTIL_DEBUG_LEVEL_WARN, + FB_UTIL_DEBUG_LEVEL_ERROR, + FB_UTIL_DEBUG_LEVEL_FATAL +} FbDebugLevel; + +/** + * FbUtilError: + * @FB_UTIL_ERROR_GENERAL: General failure. + * + * The error codes for the #FB_UTIL_ERROR domain. + */ +typedef enum +{ + FB_UTIL_ERROR_GENERAL +} FbUtilError; + +/** + * fb_util_error_quark: + * + * Gets the #GQuark of the domain of utility errors. + * + * Returns: The #GQuark of the domain. + */ +GQuark +fb_util_error_quark(void); + +/** + * fb_util_debug: + * @level: The #FbDebugLevel. + * @format: The format string literal. + * @...: The arguments for @format. + * + * Logs a debugging message. + */ +void +fb_util_debug(FbDebugLevel level, const gchar *format, ...) + G_GNUC_PRINTF(2, 3); + +/** + * fb_util_vdebug: + * @level: The #FbDebugLevel. + * @format: The format string literal. + * @ap: The #va_list. + * + * Logs a debugging message. + */ +void +fb_util_vdebug(FbDebugLevel level, const gchar *format, va_list ap); + +/** + * fb_util_debug_misc: + * @format: The format string literal. + * @...: The arguments for @format. + * + * Logs a debugging message with the level of + * #FB_UTIL_DEBUG_LEVEL_MISC. + */ +void +fb_util_debug_misc(const gchar *format, ...) + G_GNUC_PRINTF(1, 2); + +/** + * fb_util_debug_info: + * @format: The format string literal. + * @...: The arguments for @format. + * + * Logs a debugging message with the level of + * #FB_UTIL_DEBUG_LEVEL_INFO. + */ +void +fb_util_debug_info(const gchar *format, ...) + G_GNUC_PRINTF(1, 2); + +/** + * fb_util_debug_warn: + * @format: The format string literal. + * @...: The arguments for @format. + * + * Logs a debugging message with the level of + * #FB_UTIL_DEBUG_LEVEL_WARN. + */ +void +fb_util_debug_warn(const gchar *format, ...) + G_GNUC_PRINTF(1, 2); /** - * Prints a debugging line to stdout. + * fb_util_debug_error: + * @format: The format string literal. + * @...: The arguments for @format. * - * @param f The format string literal. - * @param ... The arguments for the format string. - **/ -#ifdef DEBUG_FACEBOOK -#define FB_UTIL_DEBUGLN(f, ...) \ - G_STMT_START { \ - if (fb_util_debugging()) { \ - g_print("[" PACKAGE_NAME "] " f "\n", ##__VA_ARGS__); \ - } \ - } G_STMT_END -#else /* DEBUG_FACEBOOK */ -#define FB_UTIL_DEBUGLN(f, ...) -#endif /* DEBUG_FACEBOOK */ + * Logs a debugging message with the level of + * #FB_UTIL_DEBUG_LEVEL_ERROR. + */ +void +fb_util_debug_error(const gchar *format, ...) + G_GNUC_PRINTF(1, 2); +/** + * fb_util_debug_fatal: + * @format: The format string literal. + * @...: The arguments for @format. + * + * Logs a debugging message with the level of + * #FB_UTIL_DEBUG_LEVEL_FATAL. + */ +void +fb_util_debug_fatal(const gchar *format, ...) + G_GNUC_PRINTF(1, 2); -#ifdef DEBUG_FACEBOOK -gboolean fb_util_debugging(void); -#endif /* DEBUG_FACEBOOK */ +/** + * fb_util_debug_hexdump: + * @level: The #FbDebugLevel. + * @bytes: The #GByteArray. + * @format: The format string literal. + * @...: The arguments for @format. + * + * Logs a hexdump of a #GByteArray. + */ +void +fb_util_debug_hexdump(FbDebugLevel level, const GByteArray *bytes, + const gchar *format, ...) + G_GNUC_PRINTF(3, 4); -#ifdef DEBUG_FACEBOOK -void fb_util_hexdump(const GByteArray *bytes, guint indent, - const gchar *fmt, ...) - G_GNUC_PRINTF(3, 4); -#else /* DEBUG_FACEBOOK */ -#define fb_util_hexdump(bs, i, f, ...) -#endif /* DEBUG_FACEBOOK */ +/** + * fb_util_locale_str: + * + * Gets the locale string (ex: en_US) from the system. The returned + * string should be freed with #g_free() when no longer needed. + * + * Returns: The locale string. + */ +gchar * +fb_util_locale_str(void); -gboolean fb_util_str_iequal(const gchar *s1, const gchar *s2); +/** + * fb_util_randstr: + * @size: The size of the string. + * + * Gets a random alphanumeric string. The returned string should be + * freed with #g_free() when no longer needed. + * + * Returns: The random string. + */ +gchar * +fb_util_randstr(gsize size); -gboolean fb_util_zcompressed(const GByteArray *bytes); +/** + * fb_util_str_is: + * @str: The string. + * @type: The #GAsciiType. + * + * Determines if @str abides to the #GAsciiType. + * + * Returns: #TRUE if the string abides to @type, otherwise #FALSE. + */ +gboolean +fb_util_str_is(const gchar *str, GAsciiType type); -GByteArray *fb_util_zcompress(const GByteArray *bytes); +/** + * fb_util_uuid: + * + * Gets a random UUID string. The returned string should be freed with + * #g_free() when no longer needed. + * + * Returns: The UUID string. + */ +gchar * +fb_util_uuid(void); + +/** + * fb_util_zcompressed: + * @bytes: The #GByteArray. + * + * Determines if the #GByteArray is zlib compressed. + * + * Returns: #TRUE if the #GByteArray is compressed, otherwise #FALSE. + */ +gboolean +fb_util_zcompressed(const GByteArray *bytes); -GByteArray *fb_util_zuncompress(const GByteArray *bytes); +/** + * fb_util_zcompress: + * @bytes: The #GByteArray. + * + * Compresses a #GByteArray with zlib. The returned #GByteArray should + * be freed with #g_byte_array_free() when no longer needed. + * + * Returns: The compressed #GByteArray. + */ +GByteArray * +fb_util_zcompress(const GByteArray *bytes); + +/** + * fb_util_zuncompress: + * @bytes: The #GByteArray. + * + * Uncompresses a #GByteArray with zlib. The returned #GByteArray + * should be freed with #g_byte_array_free() when no longer needed. + * + * Returns: The uncompressed #GByteArray, or #NULL on error. + */ +GByteArray * +fb_util_zuncompress(const GByteArray *bytes); -#endif /* _FACEBOOK_UTIL_H */ +#endif /* _FACEBOOK_UTIL_H_ */ diff --git a/facebook/facebook.c b/facebook/facebook.c index c25232e..61bc04d 100644 --- a/facebook/facebook.c +++ b/facebook/facebook.c @@ -15,187 +15,341 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include "facebook.h" +#include <bitlbee.h> + +#include "facebook-api.h" +#include "facebook-data.h" +#include "facebook-mqtt.h" #include "facebook-util.h" -/** - * Implemented #fb_api_funcs->error(). - * - * @param api The #fb_api. - * @param err The #GError. - * @param data The user defined data, which is #fb_data. - **/ -static void fb_cb_api_error(fb_api_t *api, GError *err, gpointer data) +static void +fb_cb_api_messages(FbApi *api, GSList *msgs, gpointer data); + +static void +fb_cb_api_auth(FbApi *api, gpointer data) { - fb_data_t *fata = data; + FbData *fata = data; + struct im_connection *ic; + + ic = fb_data_get_connection(fata); - FB_UTIL_DEBUGLN("Error: %s", err->message); - imcb_error(fata->ic, "%s", err->message); - imc_logout(fata->ic, TRUE); + imcb_log(ic, "Fetching contacts"); + fb_data_save(fata); + fb_api_contacts(api); } -/** - * Implemented #fb_api_funcs->auth(). - * - * @param api The #fb_api. - * @param data The user defined data, which is #fb_data. - **/ -static void fb_cb_api_auth(fb_api_t *api, gpointer data) +static void +fb_cb_api_connect(FbApi *api, gpointer data) { - fb_data_t *fata = data; - account_t *acc = fata->ic->acc; - gchar uid[FB_ID_STRMAX]; + account_t *acct; + FbData *fata = data; + struct im_connection *ic; + + ic = fb_data_get_connection(fata); + acct = ic->acc; - FB_ID_TO_STR(api->uid, uid); - set_setstr(&acc->set, "uid", uid); - set_setstr(&acc->set, "token", api->token); - imcb_log(fata->ic, "Authentication finished"); + fb_data_save(fata); + imcb_connected(ic); - account_off(acc->bee, acc); - account_on(acc->bee, acc); + if (set_getbool(&acct->set, "show_unread")) { + fb_api_unread(api); + } } -/** - * Implemented #fb_api_funcs->connect(). - * - * @param api The #fb_api. - * @param data The user defined data, which is #fb_data. - **/ -static void fb_cb_api_connect(fb_api_t *api, gpointer data) +static void +fb_cb_api_contact(FbApi *api, FbApiUser *user, gpointer data) { - fb_data_t *fata = data; - account_t *acc = fata->ic->acc; + FbData *fata = data; + gchar uid[FB_ID_STRMAX]; + GSList *msgs; + struct im_connection *ic; - imcb_connected(fata->ic); - set_setstr(&acc->set, "stoken", api->stoken); + ic = fb_data_get_connection(fata); + FB_ID_TO_STR(user->uid, uid); + + if (bee_user_by_handle(ic->bee, ic, uid) == NULL) { + bee_user_new(ic->bee, ic, uid, BEE_USER_LOCAL); + imcb_buddy_nick_hint(ic, uid, user->name); + imcb_rename_buddy(ic, uid, user->name); + } + + msgs = fb_data_take_messages(fata, user->uid); + + if (msgs != NULL) { + fb_cb_api_messages(api, msgs, fata); + g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free); + } } -/** - * Implemented #fb_api_funcs->contacts(). - * - * @param api The #fb_api. - * @param users The #GSList of #fb_api_user. - * @param data The user defined data, which is #fb_data. - **/ -static void fb_cb_api_contacts(fb_api_t *api, GSList *users, gpointer data) +static gboolean +fb_cb_sync_contacts(gpointer data, gint fd, b_input_condition cond) +{ + FbApi *api; + FbData *fata = data; + + api = fb_data_get_api(fata); + fb_data_clear_timeout(fata, "sync-contacts", FALSE); + fb_api_contacts(api); + return FALSE; +} + +static void +fb_cb_api_contacts(FbApi *api, GSList *users, gboolean complete, gpointer data) { - fb_data_t *fata = data; - fb_api_user_t *user; - GSList *l; - gchar uid[FB_ID_STRMAX]; + account_t *acct; + bee_user_t *bu; + FbApiUser *user; + FbData *fata = data; + FbId muid; + gchar uid[FB_ID_STRMAX]; + gint sync; + GSList *l; + GValue val = G_VALUE_INIT; + struct im_connection *ic; + + ic = fb_data_get_connection(fata); + + g_value_init(&val, FB_TYPE_ID); + g_object_get_property(G_OBJECT(api), "uid", &val); + muid = g_value_get_int64(&val); + g_value_unset(&val); for (l = users; l != NULL; l = l->next) { user = l->data; FB_ID_TO_STR(user->uid, uid); - imcb_add_buddy(fata->ic, uid, NULL); - imcb_buddy_nick_hint(fata->ic, uid, user->name); - imcb_rename_buddy(fata->ic, uid, user->name); + + if (G_UNLIKELY(user->uid == muid)) { + continue; + } + + imcb_add_buddy(ic, uid, NULL); + imcb_buddy_nick_hint(ic, uid, user->name); + imcb_rename_buddy(ic, uid, user->name); + + bu = imcb_buddy_by_handle(ic, uid); + bu->data = GINT_TO_POINTER(TRUE); + } + + if (!complete) { + return; + } + + l = ic->bee->users; + + while (l != NULL) { + bu = l->data; + l = l->next; + + if (bu->ic != ic) { + continue; + } + + if (GPOINTER_TO_INT(bu->data)) { + bu->data = NULL; + } else { + imcb_remove_buddy(ic, bu->handle, NULL); + } } - imcb_log(fata->ic, "Establishing connection"); - fb_api_connect(fata->api); + if (!(ic->flags & OPT_LOGGED_IN)) { + imcb_log(ic, "Connecting"); + fb_api_connect(api, FALSE); + } + + acct = ic->acc; + sync = set_getint(&acct->set, "sync_interval"); + + if (sync < 5) { + set_setint(&acct->set, "sync_interval", 5); + sync = 5; + } + + sync *= 60 * 1000; + fb_data_add_timeout(fata, "sync-contacts", sync, fb_cb_sync_contacts, + fata); } -/** - * Implemented #fb_api_funcs->message(). - * - * @param api The #fb_api. - * @param msgs The #GSList of #fb_api_msg. - * @param data The user defined data, which is #fb_data. - **/ -static void fb_cb_api_message(fb_api_t *api, GSList *msgs, gpointer data) +static void +fb_cb_api_error(FbApi *api, GError *error, gpointer data) +{ + FbData *fata = data; + gboolean recon; + struct im_connection *ic; + + if (g_error_matches(error, FB_API_ERROR, FB_API_ERROR_QUEUE)) { + /* Save the reset data */ + fb_data_save(fata); + } + + recon = ((error->domain != FB_HTTP_ERROR) || + (error->code < 400) || + (error->code > 500)) && + !g_error_matches(error, FB_API_ERROR, FB_API_ERROR_AUTH); + + ic = fb_data_get_connection(fata); + fb_util_debug_error("%s", error->message); + imcb_error(ic, "%s", error->message); + imc_logout(ic, recon); +} + +static void +fb_cb_api_events(FbApi *api, GSList *events, gpointer data) { - fb_data_t *fata = data; - fb_api_msg_t *msg; + FbApiEvent *event; + FbData *fata = data; + gchar tid[FB_ID_STRMAX]; + gchar uid[FB_ID_STRMAX]; + GHashTable *fetch; + GHashTableIter iter; + GSList *l; struct groupchat *gc; - GSList *l; - gchar uid[FB_ID_STRMAX]; - gchar tid[FB_ID_STRMAX]; + struct im_connection *ic; + + ic = fb_data_get_connection(fata); + fetch = g_hash_table_new(fb_id_hash, fb_id_equal); + + for (l = events; l != NULL; l = l->next) { + event = l->data; + + FB_ID_TO_STR(event->tid, tid); + gc = bee_chat_by_title(ic->bee, ic, tid); + + if (gc == NULL) { + continue; + } + + FB_ID_TO_STR(event->uid, uid); + + switch (event->type) { + case FB_API_EVENT_TYPE_THREAD_USER_ADDED: + if (bee_user_by_handle(ic->bee, ic, uid) == NULL) { + g_hash_table_insert(fetch, &event->tid, event); + break; + } + + imcb_chat_add_buddy(gc, uid); + break; + + case FB_API_EVENT_TYPE_THREAD_USER_REMOVED: + imcb_chat_remove_buddy(gc, uid, NULL); + break; + } + } + + g_hash_table_iter_init(&iter, fetch); + + while (g_hash_table_iter_next(&iter, NULL, (gpointer) &event)) { + fb_api_thread(api, event->tid); + } + + g_hash_table_destroy(fetch); +} + +static void +fb_cb_api_messages(FbApi *api, GSList *msgs, gpointer data) +{ + account_t *acct; + FbApiMessage *msg; + FbData *fata = data; + gboolean mark; + gchar tid[FB_ID_STRMAX]; + gchar uid[FB_ID_STRMAX]; + GSList *l; + struct groupchat *gc; + struct im_connection *ic; + + ic = fb_data_get_connection(fata); + acct = ic->acc; + mark = set_getbool(&acct->set, "mark_read"); for (l = msgs; l != NULL; l = l->next) { msg = l->data; FB_ID_TO_STR(msg->uid, uid); + if (msg->flags & FB_API_MESSAGE_FLAG_SELF) { + continue; + } + + if (bee_user_by_handle(ic->bee, ic, uid) == NULL) { + msg = fb_api_message_dup(msg, TRUE); + fb_data_add_message(fata, msg); + fb_api_contact(api, msg->uid); + continue; + } + if (msg->tid == 0) { - imcb_buddy_msg(fata->ic, uid, (gchar*) msg->text, 0, 0); + if (mark) { + fb_api_read(api, msg->uid, FALSE); + } + + imcb_buddy_msg(ic, uid, (gchar*) msg->text, 0, 0); continue; } FB_ID_TO_STR(msg->tid, tid); - gc = bee_chat_by_title(fata->ic->bee, fata->ic, tid); + gc = bee_chat_by_title(ic->bee, ic, tid); + + if (gc != NULL) { + if (mark) { + fb_api_read(api, msg->tid, TRUE); + } - if (gc != NULL) imcb_chat_msg(gc, uid, (gchar*) msg->text, 0, 0); + } } } -/** - * Implemented #fb_api_funcs->presence(). - * - * @param api The #fb_api. - * @param press The #GSList of #fb_api_msg. - * @param data The user defined data, which is #fb_data. - **/ -static void fb_cb_api_presence(fb_api_t *api, GSList *press, gpointer data) +static void +fb_cb_api_presences(FbApi *api, GSList *press, gpointer data) { - fb_data_t *fata = data; - fb_api_pres_t *pres; - GSList *l; - guint flags; - gchar uid[FB_ID_STRMAX]; + FbApiPresence *pres; + FbData *fata = data; + gchar uid[FB_ID_STRMAX]; + GSList *l; + guint flags; + struct im_connection *ic; + + ic = fb_data_get_connection(fata); for (l = press; l != NULL; l = l->next) { - pres = l->data; - flags = 0; + pres = l->data; + FB_ID_TO_STR(pres->uid, uid); - if (pres->active) - flags |= BEE_USER_ONLINE; + if (bee_user_by_handle(ic->bee, ic, uid) == NULL) { + continue; + } + + if (pres->active) { + flags = BEE_USER_ONLINE; + } else { + flags = 0; + } FB_ID_TO_STR(pres->uid, uid); - imcb_buddy_status(fata->ic, uid, flags, NULL, NULL); + imcb_buddy_status(ic, uid, flags, NULL, NULL); } } -/** - * Implemented #fb_api_funcs->thread_create(). - * - * @param api The #fb_api. - * @param tid The thread #fb_id. - * @param data The user defined data, which is #fb_data. - **/ -static void fb_cb_api_thread_create(fb_api_t *api, fb_id_t tid, gpointer data) -{ - fb_data_t *fata = data; - account_t *acc = fata->ic->acc; - - fata->tids = g_slist_prepend(fata->tids, g_memdup(&tid, sizeof tid)); - imcb_log(fata->ic, "Created chat thread %" FB_ID_FORMAT, tid); - imcb_log(fata->ic, "Join: fbjoin %s %d <channel-name>", acc->tag, 1); -} - -/** - * Implemented #fb_api_funcs->thread_info(). - * - * @param api The #fb_api. - * @param thrd The #fb_api_thread. - * @param data The user defined data, which is #fb_data. - **/ -static void fb_cb_api_thread_info(fb_api_t *api, fb_api_thread_t *thrd, - gpointer data) +static void +fb_cb_api_thread(FbApi *api, FbApiThread *thrd, gpointer data) { - fb_data_t *fata = data; - fb_api_user_t *user; - bee_user_t *bu; + bee_user_t *bu; + FbApiUser *user; + FbData *fata = data; + gchar id[FB_ID_STRMAX]; + GList *h; + GSList *l; + GString *gstr; struct groupchat *gc; - GSList *l; - GString *gstr; - gchar id[FB_ID_STRMAX]; + struct im_connection *ic; FB_ID_TO_STR(thrd->tid, id); - gc = bee_chat_by_title(fata->ic->bee, fata->ic, id); + ic = fb_data_get_connection(fata); + gc = bee_chat_by_title(ic->bee, ic, id); - if (G_UNLIKELY(gc == NULL)) + if (G_UNLIKELY(gc == NULL)) { return; + } if (thrd->topic == NULL) { gstr = g_string_new(NULL); @@ -203,8 +357,9 @@ static void fb_cb_api_thread_info(fb_api_t *api, fb_api_thread_t *thrd, for (l = thrd->users; l != NULL; l = l->next) { user = l->data; - if (gstr->len > 0) + if (gstr->len > 0) { g_string_append(gstr, ", "); + } g_string_append(gstr, user->name); } @@ -212,220 +367,146 @@ static void fb_cb_api_thread_info(fb_api_t *api, fb_api_thread_t *thrd, imcb_chat_topic(gc, NULL, gstr->str, 0); g_string_free(gstr, TRUE); } else { - imcb_chat_topic(gc, NULL, (gchar*) thrd->topic, 0); + imcb_chat_topic(gc, NULL, (gchar *) thrd->topic, 0); } for (l = thrd->users; l != NULL; l = l->next) { user = l->data; FB_ID_TO_STR(user->uid, id); - bu = bee_user_by_handle(fata->ic->bee, fata->ic, id); + h = g_list_find_custom(gc->in_room, id, (GCompareFunc) g_strcmp0); - imcb_chat_add_buddy(gc, id); + if (h != NULL) { + continue; + } + + bu = bee_user_by_handle(ic->bee, ic, id); if (bu == NULL) { - imcb_buddy_nick_hint(fata->ic, id, user->name); - imcb_rename_buddy(fata->ic, id, user->name); + bee_user_new(ic->bee, ic, id, BEE_USER_LOCAL); + imcb_buddy_nick_hint(ic, id, user->name); + imcb_rename_buddy(ic, id, user->name); } + + imcb_chat_add_buddy(gc, id); } } -/** - * Implemented #fb_api_funcs->thread_list(). - * - * @param api The #fb_api. - * @param thrds The #GSList of #fb_api_thread. - * @param data The user defined data, which is #fb_data. - **/ -static void fb_cb_api_thread_list(fb_api_t *api, GSList *thrds, gpointer data) +static void +fb_cb_api_thread_create(FbApi *api, FbId tid, gpointer data) { - fb_data_t *fata = data; - fb_api_thread_t *thrd; - fb_api_user_t *user; - GSList *phrds; - GSList *l; - GSList *m; - GString *ln; - gpointer mptr; - guint i; - guint j; - - g_slist_free_full(fata->tids, g_free); - fata->tids = NULL; - phrds = NULL; + account_t *acct; + FbData *fata = data; + struct im_connection *ic; + + ic = fb_data_get_connection(fata); + acct = ic->acc; + + fb_data_add_thread_head(fata, tid); + imcb_log(ic, "Created chat thread %" FB_ID_FORMAT, tid); + imcb_log(ic, "Join: fbjoin %s %d <channel-name>", acct->tag, 1); +} + +static void +fb_cb_api_threads(FbApi *api, GSList *thrds, gpointer data) +{ + FbApiThread *thrd; + FbApiUser *user; + FbData *fata = data; + GSList *phrds = NULL; + GSList *l; + GSList *m; + GString *lines; + guint i; + guint j; + struct im_connection *ic; + + ic = fb_data_get_connection(fata); + fb_data_clear_threads(fata); for (l = thrds, i = 0; (l != NULL) && (i < 25); l = l->next, i++) { thrd = l->data; - if (g_slist_length(thrd->users) >= 2) + if (g_slist_length(thrd->users) >= 2) { phrds = g_slist_prepend(phrds, thrd); + } } if (phrds == NULL) { - imcb_log(fata->ic, "No chats to display."); + imcb_log(ic, "No chats to display."); return; } - ln = g_string_new(NULL); - imcb_log(fata->ic, "%2s %-20s %s", "ID", "Topic", "Participants"); + lines = g_string_new(NULL); + imcb_log(ic, "%2s %-20s %s", "ID", "Topic", "Participants"); phrds = g_slist_reverse(phrds); for (l = phrds, i = 1; l != NULL; l = l->next, i++) { thrd = l->data; - - if (g_slist_length(thrd->users) < 2) - continue; - - mptr = g_memdup(&thrd->tid, sizeof thrd->tid); - fata->tids = g_slist_prepend(fata->tids, mptr); - - g_string_printf(ln, "%2d", i); + fb_data_add_thread_tail(fata, thrd->tid); + g_string_printf(lines, "%2d", i); if (thrd->topic != NULL) { if (strlen(thrd->topic) > 20) { for (j = 16; g_ascii_isspace(thrd->topic[j]) && (j > 0); j--); - g_string_append_printf(ln, " %-.*s...", ++j, thrd->topic); - g_string_append_printf(ln, "%*s", 17 - j, ""); + g_string_append_printf(lines, " %-.*s...", ++j, thrd->topic); + g_string_append_printf(lines, "%*s", 17 - j, ""); } else { - g_string_append_printf(ln, " %-20s", thrd->topic); + g_string_append_printf(lines, " %-20s", thrd->topic); } } else { - g_string_append_printf(ln, " %20s", ""); + g_string_append_printf(lines, " %20s", ""); } for (m = thrd->users, j = 0; (m != NULL) && (j < 3); m = m->next, j++) { user = m->data; - g_string_append(ln, (j != 0) ? ", " : " "); - g_string_append(ln, user->name); + g_string_append(lines, (j != 0) ? ", " : " "); + g_string_append(lines, user->name); } - if (m != NULL) - g_string_append(ln, "..."); + if (m != NULL) { + g_string_append(lines, "..."); + } - imcb_log(fata->ic, "%s", ln->str); + imcb_log(ic, "%s", lines->str); } - fata->tids = g_slist_reverse(fata->tids); - g_string_free(ln, TRUE); + g_string_free(lines, TRUE); g_slist_free(phrds); } -/** - * Implemented #fb_api_funcs->typing(). - * - * @param api The #fb_api. - * @param typg The #fb_api_typing. - * @param data The user defined data, which is #fb_data. - **/ -static void fb_cb_api_typing(fb_api_t *api, fb_api_typing_t *typg, - gpointer data) +static void +fb_cb_api_typing(FbApi *api, FbApiTyping *typg, gpointer data) { - fb_data_t *fata = data; - guint32 flags; - gchar uid[FB_ID_STRMAX]; + FbData *fata = data; + gchar uid[FB_ID_STRMAX]; + guint32 flags; + struct im_connection *ic; FB_ID_TO_STR(typg->uid, uid); + ic = fb_data_get_connection(fata); flags = typg->state ? OPT_TYPING : 0; - imcb_buddy_typing(fata->ic, uid, flags); + imcb_buddy_typing(ic, uid, flags); } -/** - * Creates a new #fb_data with an #account. The returned #fb_data - * should be freed with #fb_data_free() when no longer needed. - * - * @param acc The #account. - * - * @return The #fb_data or NULL on error. - **/ -fb_data_t *fb_data_new(account_t *acc) -{ - fb_data_t *fata; - gchar *uid; - - static const fb_api_funcs_t funcs = { - .error = fb_cb_api_error, - .auth = fb_cb_api_auth, - .connect = fb_cb_api_connect, - .contacts = fb_cb_api_contacts, - .message = fb_cb_api_message, - .presence = fb_cb_api_presence, - .thread_create = fb_cb_api_thread_create, - .thread_info = fb_cb_api_thread_info, - .thread_list = fb_cb_api_thread_list, - .typing = fb_cb_api_typing - }; - - g_return_val_if_fail(acc != NULL, NULL); - - fata = g_new0(fb_data_t, 1); - fata->api = fb_api_new(&funcs, fata); - - fata->ic = imcb_new(acc); - fata->ic->proto_data = fata; - - uid = set_getstr(&acc->set, "uid"); - - if (uid != NULL) - fata->api->uid = FB_ID_FROM_STR(uid); - - fata->api->token = g_strdup(set_getstr(&acc->set, "token")); - fata->api->stoken = g_strdup(set_getstr(&acc->set, "stoken")); - fata->api->cid = g_strdup(set_getstr(&acc->set, "cid")); - fata->api->mid = g_strdup(set_getstr(&acc->set, "mid")); - fata->api->cuid = g_strdup(set_getstr(&acc->set, "cuid")); - - fb_api_rehash(fata->api); - - set_setstr(&acc->set, "cid", fata->api->cid); - set_setstr(&acc->set, "mid", fata->api->mid); - set_setstr(&acc->set, "cuid", fata->api->cuid); - - return fata; -} - -/** - * Frees all memory used by a #fb_data. - * - * @param sata The #fb_data. - **/ -void fb_data_free(fb_data_t *fata) -{ - if (G_UNLIKELY(fata == NULL)) - return; - - fb_api_free(fata->api); - g_slist_free_full(fata->tids, g_free); - g_slist_free_full(fata->gcs, (GDestroyNotify) imcb_chat_free); - g_free(fata); -} - -/** - * Creates a new #groupchat and adds it to the #fb_data. The returned - * #groupchat should be freed with #fb_data_groupchat_free() when no - * longer needed. - * - * @param ic The #im_connection. - * @param tid The thread #fb_id. - * @param name The name of the channel. - * - * @return The #groupchat or NULL on error. - **/ -struct groupchat *fb_data_groupchat_new(struct im_connection *ic, - fb_id_t tid, - const gchar *name) +static struct groupchat * +fb_data_groupchat_new(struct im_connection *ic, FbId tid, const gchar *name) { - fb_data_t *fata = ic->proto_data; + FbApi *api; + FbData *fata = ic->proto_data; + gchar stid[FB_ID_STRMAX]; + irc_channel_t *ch; struct groupchat *gc; - irc_channel_t *ch; - gchar stid[FB_ID_STRMAX]; FB_ID_TO_STR(tid, stid); - if (bee_chat_by_title(ic->bee, ic, stid) != NULL) + if (bee_chat_by_title(ic->bee, ic, stid) != NULL) { return NULL; + } if (name != NULL) { - if (strchr(CTYPES, name[0]) != NULL) + if (strchr(CTYPES, name[0]) != NULL) { name++; + } /* Let the hackery being... */ gc = imcb_chat_new(ic, stid); @@ -445,385 +526,257 @@ struct groupchat *fb_data_groupchat_new(struct im_connection *ic, } gc = imcb_chat_new(ic, stid); - ch = gc->ui_data; - fata->gcs = g_slist_prepend(fata->gcs, gc); + fb_data_add_groupchat(fata, gc); + ch = gc->ui_data; ch->flags &= ~IRC_CHANNEL_CHAT_PICKME; - imcb_chat_add_buddy(gc, ic->acc->user); - fb_api_thread_info(fata->api, tid); + api = fb_data_get_api(fata); + imcb_chat_add_buddy(gc, ic->acc->user); + fb_api_thread(api, tid); return gc; } -/** - * Frees all memory used by a #groupchat and removes it from the - * #fb_data. - * - * @param gc The #groupchat. - **/ -void fb_data_groupchat_free(struct groupchat *gc) -{ - fb_data_t *fata; - - if (G_UNLIKELY(gc == NULL)) - return; - - if (G_LIKELY(gc->ic != NULL)) { - fata = gc->ic->proto_data; - fata->gcs = g_slist_remove(fata->gcs, gc); - } - - imcb_chat_free(gc); -} - -/** - * Implements #prpl->init(). This initializes an account. - * - * @param acc The #account. - **/ -static void fb_init(account_t *acc) +static void +fb_init(account_t *acct) { set_t *s; - s = set_add(&acc->set, "cid", NULL, NULL, acc); + s = set_add(&acct->set, "cid", NULL, NULL, acct); s->flags = SET_NULL_OK | SET_HIDDEN; - s = set_add(&acc->set, "cuid", NULL, NULL, acc); + s = set_add(&acct->set, "did", NULL, NULL, acct); s->flags = SET_NULL_OK | SET_HIDDEN; - s = set_add(&acc->set, "mid", NULL, NULL, acc); + s = set_add(&acct->set, "mid", NULL, NULL, acct); s->flags = SET_NULL_OK | SET_HIDDEN; - s = set_add(&acc->set, "token", NULL, NULL, acc); + s = set_add(&acct->set, "token", NULL, NULL, acct); s->flags = SET_NULL_OK | SET_HIDDEN | SET_PASSWORD; - s = set_add(&acc->set, "stoken", NULL, NULL, acc); + s = set_add(&acct->set, "stoken", NULL, NULL, acct); s->flags = SET_NULL_OK | SET_HIDDEN; - s = set_add(&acc->set, "uid", NULL, NULL, acc); + s = set_add(&acct->set, "uid", NULL, NULL, acct); s->flags = SET_NULL_OK | SET_HIDDEN; + + set_add(&acct->set, "mark_read", "true", set_eval_bool, acct); + set_add(&acct->set, "show_unread", "true", set_eval_bool, acct); + set_add(&acct->set, "sync_interval", "30", set_eval_int, acct); } -/** - * Implements #prpl->login(). This logins an account in. - * - * @param acc The #account. - **/ -static void fb_login(account_t *acc) +static void +fb_login(account_t *acc) { - fb_data_t *fata; + FbApi *api; + FbData *fata; + struct im_connection *ic; fata = fb_data_new(acc); - imcb_log(fata->ic, "Connecting"); - - if (fata->api->token == NULL) { - imcb_log(fata->ic, "Requesting authentication token"); - fb_api_auth(fata->api, acc->user, acc->pass); + api = fb_data_get_api(fata); + ic = fb_data_get_connection(fata); + ic->proto_data = fata; + + g_signal_connect(api, + "auth", + G_CALLBACK(fb_cb_api_auth), + fata); + g_signal_connect(api, + "connect", + G_CALLBACK(fb_cb_api_connect), + fata); + g_signal_connect(api, + "contact", + G_CALLBACK(fb_cb_api_contact), + fata); + g_signal_connect(api, + "contacts", + G_CALLBACK(fb_cb_api_contacts), + fata); + g_signal_connect(api, + "error", + G_CALLBACK(fb_cb_api_error), + fata); + g_signal_connect(api, + "events", + G_CALLBACK(fb_cb_api_events), + fata); + g_signal_connect(api, + "messages", + G_CALLBACK(fb_cb_api_messages), + fata); + g_signal_connect(api, + "presences", + G_CALLBACK(fb_cb_api_presences), + fata); + g_signal_connect(api, + "thread", + G_CALLBACK(fb_cb_api_thread), + fata); + g_signal_connect(api, + "thread-create", + G_CALLBACK(fb_cb_api_thread_create), + fata); + g_signal_connect(api, + "threads", + G_CALLBACK(fb_cb_api_threads), + fata); + g_signal_connect(api, + "typing", + G_CALLBACK(fb_cb_api_typing), + fata); + + if (!fb_data_load(fata)) { + imcb_log(ic, "Authenticating"); + fb_api_auth(api, acc->user, acc->pass); return; } - imcb_log(fata->ic, "Fetching contacts"); - fb_api_contacts(fata->api); + imcb_log(ic, "Fetching contacts"); + fb_api_contacts(api); } -/** - * Implements #prpl->logout(). This logs an account out. - * - * @param ic The #im_connection. - **/ -static void fb_logout(struct im_connection *ic) +static void +fb_logout(struct im_connection *ic) { - fb_data_t *fata = ic->proto_data; + FbApi *api; + FbData *fata = ic->proto_data; - if (fata->api->stoken == NULL) { - set_reset(&ic->acc->set, "stoken"); - } + api = fb_data_get_api(fata); + ic->proto_data = NULL; - fb_api_disconnect(fata->api); - fb_data_free(fata); + fb_data_save(fata); + fb_api_disconnect(api); + g_object_unref(fata); } -/** - * Implements #prpl->buddy_msg(). This sends a message to a buddy. - * - * @param ic The #im_connection. - * @param to The handle of the buddy. - * @param message The message to send. - * @param flags The message flags. (Irrelevant to this plugin) - * - * @return 0. (Upstream bitlbee does nothing with this) - **/ -static int fb_buddy_msg(struct im_connection *ic, char *to, char *message, - int flags) +static int +fb_buddy_msg(struct im_connection *ic, char *to, char *message, int flags) { - fb_data_t *fata = ic->proto_data; - fb_id_t uid; + FbApi *api; + FbData *fata = ic->proto_data; + FbId uid; + api = fb_data_get_api(fata); uid = FB_ID_FROM_STR(to); - fb_api_message(fata->api, uid, FALSE, message); + fb_api_message(api, uid, FALSE, message); return 0; } -/** - * Implements #prpl->send_typing(). This sends the typing state message. - * - * @param ic The #im_connection. - * @param who The handle of the buddy. - * @param flags The message flags. (Irrelevant to this plugin) - * - * @return 0. (Upstream bitlbe does nothing with this) - **/ -static int fb_send_typing(struct im_connection *ic, char *who, int flags) +static int +fb_send_typing(struct im_connection *ic, char *who, int flags) { - fb_data_t *fata = ic->proto_data; - fb_id_t uid; - gboolean state; + FbApi *api; + FbData *fata = ic->proto_data; + FbId uid; + gboolean state; - uid = FB_ID_FROM_STR(who); + api = fb_data_get_api(fata); + uid = FB_ID_FROM_STR(who); state = (flags & OPT_TYPING) != 0; - fb_api_typing(fata->api, uid, state); + fb_api_typing(api, uid, state); return 0; } -/** - * Implements #prpl->add_buddy(). This adds a buddy. - * - * @param ic The #im_connection. - * @param name The name of the buddy to add. - * @param group The group of the buddy. (Irrelevant to this plugin) - **/ -static void fb_add_buddy(struct im_connection *ic, char *name, char *group) +static void +fb_add_buddy(struct im_connection *ic, char *name, char *group) { } -/** - * Implements #prpl->remove_buddy(). This removes a buddy. - * - * @param ic The #im_connection. - * @param name The name of the buddy to add. - * @param group The group of the buddy. (Irrelevant to this plugin) - **/ -static void fb_remove_buddy(struct im_connection *ic, char *name, char *group) +static void +fb_remove_buddy(struct im_connection *ic, char *name, char *group) { } -/** - * Implements #prpl->add_permit(). This is not used by the plugin. - * - * @param ic The #im_connection. - * @param who The handle of the buddy. - **/ -static void fb_add_permit(struct im_connection *ic, char *who) +static void +fb_chat_invite(struct groupchat *gc, char *who, char *message) { + FbApi *api; + FbData *fata = gc->ic->proto_data; + FbId tid; + FbId uid; -} - -/** - * Implements #prpl->add_deny(). This blocks a buddy. - * - * @param ic The #im_connection. - * @param who The handle of the buddy. - **/ -static void fb_add_deny(struct im_connection *ic, char *who) -{ - -} - -/** - * Implements #prpl->rem_permit(). This is not used by the plugin. - * - * @param ic The #im_connection. - * @param who The handle of the buddy. - **/ -static void fb_rem_permit(struct im_connection *ic, char *who) -{ - -} - -/** - * Implements #prpl->rem_deny(). This unblocks a buddy. - * - * @param ic The #im_connection. - * @param who The handle of the buddy. - **/ -static void fb_rem_deny(struct im_connection *ic, char *who) -{ - -} - -/** - * Implements #prpl->get_info(). This retrieves the info of a buddy. - * - * @param ic The #im_connection. - * @param who The handle of the buddy. - **/ -static void fb_get_info(struct im_connection *ic, char *who) -{ - -} - -/** - * Implements #prpl->chat_invite(). This invites a user to a #groupchat. - * - * @param gc The #groupchat. - * @param who Ignored. - * @param message The handle to invite. - **/ -void fb_chat_invite(struct groupchat *gc, char *who, char *message) -{ - fb_data_t *fata = gc->ic->proto_data; - fb_id_t tid; - fb_id_t uid; - + api = fb_data_get_api(fata); tid = FB_ID_FROM_STR(gc->title); uid = FB_ID_FROM_STR(who); - fb_api_thread_invite(fata->api, tid, uid); + + fb_api_thread_invite(api, tid, uid); imcb_chat_add_buddy(gc, who); } -/** - * Implements #prpl->chat_leave(). This leaves a #groupchat. - * - * @param gc The #groupchat. - **/ -void fb_chat_leave(struct groupchat *gc) +static void +fb_chat_leave(struct groupchat *gc) { - fb_data_groupchat_free(gc); + FbData *fata = gc->ic->proto_data; + + fb_data_remove_groupchat(fata, gc); + imcb_chat_free(gc); } -/** - * Implements #prpl->chat_msg(). This sends a message to a #groupchat. - * - * @param gc The #groupchat. - * @param message The message to send. - * @param flags Ignored. - **/ -void fb_chat_msg(struct groupchat *gc, char *message, int flags) +static void +fb_chat_msg(struct groupchat *gc, char *message, int flags) { - fb_data_t *fata = gc->ic->proto_data; - fb_id_t tid; + FbApi *api; + FbData *fata = gc->ic->proto_data; + FbId tid; + api = fb_data_get_api(fata); tid = FB_ID_FROM_STR(gc->title); - fb_api_message(fata->api, tid, TRUE, message); + fb_api_message(api, tid, TRUE, message); } -/** - * Implements #prpl->chat_join(). This joins a #groupchat. - * - * @param ic The #im_connection. - * @param room The room name. - * @param nick The nick name. - * @param password The password. - * @param sets The #set array. - **/ -struct groupchat *fb_chat_join(struct im_connection *ic, const char *room, - const char *nick, const char *password, - set_t **sets) +static struct groupchat * +fb_chat_join(struct im_connection *ic, const char *room, const char *nick, + const char *password, set_t **sets) { - fb_data_t *fata = ic->proto_data; - fb_id_t tid; + FbId tid; struct groupchat *gc; tid = FB_ID_FROM_STR(room); - gc = fb_data_groupchat_new(ic, tid, NULL); + gc = fb_data_groupchat_new(ic, tid, NULL); if (gc == NULL) { - imcb_error(fata->ic, "Failed to join chat: %" FB_ID_FORMAT, tid); + imcb_error(ic, "Failed to join chat: %" FB_ID_FORMAT, tid); return NULL; } return gc; } -/** - * Implements #prpl->chat_topic(). This sets a #groupchat topic. - * - * @param gc The #groupchat. - * @param topic The topic - **/ -void fb_chat_topic(struct groupchat *gc, char *topic) +static void +fb_chat_topic(struct groupchat *gc, char *topic) { - fb_data_t *fata = gc->ic->proto_data; - fb_id_t tid; + FbApi *api; + FbData *fata = gc->ic->proto_data; + FbId tid; + api = fb_data_get_api(fata); tid = FB_ID_FROM_STR(gc->title); - fb_api_thread_topic(fata->api, tid, topic); + fb_api_thread_topic(api, tid, topic); imcb_chat_topic(gc, NULL, topic, 0); } -/** - * Implements #prpl->auth_allow(). This accepts buddy requests. - * - * @param ic The #im_connection. - * @param who The handle of the buddy. - **/ -static void fb_auth_allow(struct im_connection *ic, const char *who) -{ - -} - -/** - * Implements #prpl->auth_allow(). This denies buddy requests. - * - * @param ic The #im_connection. - * @param who The handle of the buddy. - **/ -static void fb_auth_deny(struct im_connection *ic, const char *who) -{ - -} - -/** - * Implements #prpl->buddy_data_add(). This adds data to the buddy. - * - * @param bu The #bee_user. - **/ -static void fb_buddy_data_add(struct bee_user *bu) -{ - -} - -/** - * Implements #prpl->buddy_data_free(). This frees the buddy data. - * - * @param bu The #bee_user. - **/ -static void fb_buddy_data_free(struct bee_user *bu) -{ - -} - -/** - * Obtains a #account from command arguments. - * - * @param irc The #irc. - * @param args The command arguments. - * @param required The amount of required arguments. - * @param offset The return location for the args offset. - * - * @return The #account or NULL on error. - **/ -static account_t *fb_cmd_account(irc_t *irc, char **args, guint required, - guint *offset) +static account_t * +fb_cmd_account(irc_t *irc, char **args, guint required, guint *offset) { account_t *a; - account_t *acc; - guint accs; - guint size; - guint oset; + account_t *acct; + guint acctc; + guint size; + guint oset; - for (accs = 0, a = irc->b->accounts; a != NULL; a = a->next) { + for (acctc= 0, a = irc->b->accounts; a != NULL; a = a->next) { if ((g_ascii_strcasecmp(a->prpl->name, "facebook") == 0) && (a->ic != NULL)) { - acc = a; - accs++; + acct = a; + acctc++; } } - if (accs == 0) { + if (acctc == 0) { irc_rootmsg(irc, "There are no active Facebook accounts!"); return NULL; } @@ -831,33 +784,33 @@ static account_t *fb_cmd_account(irc_t *irc, char **args, guint required, /* Calculate the size of args */ for (size = 1; args[size] != NULL; size++); - if (accs > 1) { + if (acctc > 1) { if (args[1] == NULL) { irc_rootmsg(irc, "More than one Facebook account, specify one."); return NULL; } /* More than one account, look up by handle */ - acc = account_get(irc->b, args[1]); + acct = account_get(irc->b, args[1]); oset = 2; - if (acc == NULL) { + if (acct == NULL) { irc_rootmsg(irc, "Unknown account: %s", args[1]); return NULL; } - if (acc->ic == NULL) { - irc_rootmsg(irc, "Account not online: %s", acc->tag); + if (acct->ic == NULL) { + irc_rootmsg(irc, "Account not online: %s", acct->tag); return NULL; } - if (g_ascii_strcasecmp(acc->prpl->name, "facebook") != 0) { - irc_rootmsg(irc, "Unknown Facebook account: %s", acc->tag); + if (g_ascii_strcasecmp(acct->prpl->name, "facebook") != 0) { + irc_rootmsg(irc, "Unknown Facebook account: %s", acct->tag); return NULL; } } else if ((size != (required + 1)) && (args[1] != NULL) && - (account_get(irc->b, args[1]) == acc)) + (account_get(irc->b, args[1]) == acct)) { /* One account with an identifier */ oset = 2; @@ -871,149 +824,138 @@ static account_t *fb_cmd_account(irc_t *irc, char **args, guint required, return NULL; } - if (offset != NULL) + if (offset != NULL) { *offset = oset; + } - return acc; + return acct; } -/** - * Implemented #root_command_add() callback for the 'fbchats' command. - * - * @param irc The #irc. - * @param args The command arguments. - **/ -static void fb_cmd_fbchats(irc_t *irc, char **args) +static void +fb_cmd_fbchats(irc_t *irc, char **args) { - account_t *acc; - fb_data_t *fata; + account_t *acct; + FbApi *api; + FbData *fata; - acc = fb_cmd_account(irc, args, 0, NULL); + acct = fb_cmd_account(irc, args, 0, NULL); - if (acc == NULL) + if (acct == NULL) { return; + } - fata = acc->ic->proto_data; - fb_api_thread_list(fata->api, 25); + fata = acct->ic->proto_data; + api = fb_data_get_api(fata); + fb_api_threads(api); } -/** - * Implemented #root_command_add() callback for the 'fbcreate' command. - * - * @param irc The #irc. - * @param args The command arguments. - **/ -static void fb_cmd_fbcreate(irc_t *irc, char **args) +static void +fb_cmd_fbcreate(irc_t *irc, char **args) { - account_t *acc; - fb_data_t *fata; - fb_id_t uid; + account_t *acct; + FbApi *api; + FbData *fata; + FbId *duid; + FbId uid; irc_user_t *iu; - GSList *uids; - guint oset; - guint i; + GSList *uids = NULL; + guint oset; + guint i; + struct im_connection *ic; - acc = fb_cmd_account(irc, args, 2, &oset); - uids = NULL; + acct = fb_cmd_account(irc, args, 2, &oset); - if (acc == NULL) + if (acct == NULL) { return; + } - fata = acc->ic->proto_data; + fata = acct->ic->proto_data; + ic = fb_data_get_connection(fata); for (i = oset; args[i] != NULL; i++) { iu = irc_user_by_name(irc, args[i]); if (iu != NULL) { uid = FB_ID_FROM_STR(iu->bu->handle); - uids = g_slist_prepend(uids, g_memdup(&uid, sizeof uid)); + duid = g_memdup(&uid, sizeof uid); + uids = g_slist_prepend(uids, duid); } } if (uids == NULL) { - imcb_error(fata->ic, "No valid users specified"); + imcb_error(ic, "No valid users specified"); return; } - fb_api_thread_create(fata->api, uids); + api = fb_data_get_api(fata); + fb_api_thread_create(api, uids); g_slist_free_full(uids, g_free); } -/** - * Implemented #root_command_add() callback for the 'fbjoin' command. - * - * @param irc The #irc. - * @param args The command arguments. - **/ -static void fb_cmd_fbjoin(irc_t *irc, char **args) +static void +fb_cmd_fbjoin(irc_t *irc, char **args) { - account_t *acc; - fb_data_t *fata; - fb_id_t *tid; - gchar *name; - guint oset; - gint64 indx; + account_t *acct; + FbData *fata; + FbId tid; + gchar *name; + guint i; + guint oset; + struct im_connection *ic; - acc = fb_cmd_account(irc, args, 2, &oset); + acct = fb_cmd_account(irc, args, 2, &oset); - if (acc == NULL) + if (acct == NULL) { return; + } + + fata = acct->ic->proto_data; + ic = fb_data_get_connection(fata); - fata = acc->ic->proto_data; name = args[oset + 1]; - indx = g_ascii_strtoll(args[oset], NULL, 10); - tid = g_slist_nth_data(fata->tids, indx - 1); + i = g_ascii_strtoll(args[oset], NULL, 10); + tid = fb_data_get_thread(fata, i - 1); - if ((indx < 1) || (tid == NULL)) { - imcb_error(fata->ic, "Invalid index: %" G_GINT64_FORMAT, indx); + if ((i < 1) || (tid == 0)) { + imcb_error(ic, "Invalid index: %u", i); return; } - if (fb_data_groupchat_new(acc->ic, *tid, name) == NULL) { - imcb_error(fata->ic, "Failed to join chat: %s (%" FB_ID_FORMAT ")", - name, *tid); + if (fb_data_groupchat_new(ic, tid, name) == NULL) { + imcb_error(ic, "Failed to join chat: %s (%" FB_ID_FORMAT ")", + name, tid); } } -/** - * Implements the #init_plugin() function. BitlBee looks for this - * function and executes it to register the protocol and its related - * callbacks. - **/ -void init_plugin() -{ - struct prpl *pp; - - pp = g_new0(struct prpl, 1); - - pp->name = "facebook"; - pp->options = OPT_NOOTR; - pp->init = fb_init; - pp->login = fb_login; - pp->logout = fb_logout; - pp->buddy_msg = fb_buddy_msg; - pp->send_typing = fb_send_typing; - pp->add_buddy = fb_add_buddy; - pp->remove_buddy = fb_remove_buddy; - pp->add_permit = fb_add_permit; - pp->add_deny = fb_add_deny; - pp->rem_permit = fb_rem_permit; - pp->rem_deny = fb_rem_deny; - pp->get_info = fb_get_info; - pp->chat_invite = fb_chat_invite; - pp->chat_leave = fb_chat_leave; - pp->chat_msg = fb_chat_msg; - pp->chat_join = fb_chat_join; - pp->chat_topic = fb_chat_topic; - pp->handle_cmp = g_ascii_strcasecmp; - pp->auth_allow = fb_auth_allow; - pp->auth_deny = fb_auth_deny; - pp->buddy_data_add = fb_buddy_data_add; - pp->buddy_data_free = fb_buddy_data_free; - - register_protocol(pp); - - root_command_add("fbchats", 0, fb_cmd_fbchats, 0); +G_MODULE_EXPORT void +init_plugin(void); + +G_MODULE_EXPORT void +init_plugin(void) +{ + struct prpl *dpp; + + static const struct prpl pp = { + .name = "facebook", + .init = fb_init, + .login = fb_login, + .logout = fb_logout, + .buddy_msg = fb_buddy_msg, + .send_typing = fb_send_typing, + .add_buddy = fb_add_buddy, + .remove_buddy = fb_remove_buddy, + .chat_invite = fb_chat_invite, + .chat_leave = fb_chat_leave, + .chat_msg = fb_chat_msg, + .chat_join = fb_chat_join, + .chat_topic = fb_chat_topic, + .handle_cmp = g_strcmp0 + }; + + dpp = g_memdup(&pp, sizeof pp); + register_protocol(dpp); + + root_command_add("fbchats", 0, fb_cmd_fbchats, 0); root_command_add("fbcreate", 0, fb_cmd_fbcreate, 0); - root_command_add("fbjoin", 0, fb_cmd_fbjoin, 0); + root_command_add("fbjoin", 0, fb_cmd_fbjoin, 0); } diff --git a/facebook/facebook.h b/facebook/facebook.h deleted file mode 100644 index 9ee32f6..0000000 --- a/facebook/facebook.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2014-2015 James Geboski <jgeboski@gmail.com> - * - * 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, see <http://www.gnu.org/licenses/>. - */ - -/** @file **/ - -#ifndef _FACEBOOK_H -#define _FACEBOOK_H - -#include <bitlbee.h> - -#include "facebook-api.h" -#include "facebook-mqtt.h" - -/** The main structure for the plugin. **/ -typedef struct fb_data fb_data_t; - - -/** - * The main structure for the plugin. - **/ -struct fb_data -{ - struct im_connection *ic; /** The #im_connection. **/ - fb_api_t *api; /** The #fb_api. **/ - GSList *gcs; /** The #GSList of #groupchats. **/ - GSList *tids; /** The #GSList of thread identifiers. **/ -}; - - -fb_data_t *fb_data_new(account_t *acc); - -void fb_data_free(fb_data_t *fata); - -struct groupchat *fb_data_groupchat_new(struct im_connection *ic, - fb_id_t tid, - const gchar *name); - -void fb_data_groupchat_free(struct groupchat *gc); - -#endif /* _FACEBOOK_H */ diff --git a/facebook/marshaller.list b/facebook/marshaller.list new file mode 100644 index 0000000..ab96190 --- /dev/null +++ b/facebook/marshaller.list @@ -0,0 +1,6 @@ +VOID:INT64 +VOID:OBJECT +VOID:POINTER +VOID:POINTER,BOOLEAN +VOID:STRING,BOXED +VOID:VOID diff --git a/valgrind.supp b/valgrind.supp new file mode 100644 index 0000000..4225021 --- /dev/null +++ b/valgrind.supp @@ -0,0 +1,39 @@ +{ + leak:conf_load + Memcheck:Leak + ... + fun:conf_load + fun:main +} + +{ + leak:gobject_init_ctor + Memcheck:Leak + match-leak-kinds: possible + ... + fun:gobject_init_ctor +} + +{ + leak:g_signal_new + Memcheck:Leak + match-leak-kinds: possible + ... + fun:g_signal_new +} + +{ + leak:g_type_create_instance + Memcheck:Leak + match-leak-kinds: possible + ... + fun:g_type_create_instance +} + +{ + leak:g_type_register_static + Memcheck:Leak + match-leak-kinds: possible + ... + fun:g_type_register_static +} |