aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml7
-rw-r--r--README12
-rw-r--r--configure.ac50
-rw-r--r--debian/control4
-rw-r--r--facebook/Makefile.am48
-rw-r--r--facebook/facebook-api.c3585
-rw-r--r--facebook/facebook-api.h979
-rw-r--r--facebook/facebook-data.c404
-rw-r--r--facebook/facebook-data.h257
-rw-r--r--facebook/facebook-http.c1174
-rw-r--r--facebook/facebook-http.h581
-rw-r--r--facebook/facebook-id.h120
-rw-r--r--facebook/facebook-json.c777
-rw-r--r--facebook/facebook-json.h502
-rw-r--r--facebook/facebook-mqtt.c1259
-rw-r--r--facebook/facebook-mqtt.h724
-rw-r--r--facebook/facebook-thrift.c1005
-rw-r--r--facebook/facebook-thrift.h617
-rw-r--r--facebook/facebook-util.c383
-rw-r--r--facebook/facebook-util.h249
-rw-r--r--facebook/facebook.c1274
-rw-r--r--facebook/facebook.h54
-rw-r--r--facebook/marshaller.list6
-rw-r--r--valgrind.supp39
25 files changed, 9339 insertions, 4772 deletions
diff --git a/.gitignore b/.gitignore
index ae735a1..1c0c45f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/README b/README
index 5b99949..6b9d960 100644
--- a/README
+++ b/README
@@ -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
+}