summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMathias Hasselmann <hasselmm@gnome.org>2009-11-06 10:37:20 +0100
committerMathias Hasselmann <hasselmm@gnome.org>2009-11-06 10:37:20 +0100
commita94a7f46ec448c1415dc68203222c46e3f2a5741 (patch)
treefc3342620ba840152edc8afba069acbd497ef26e
parentbcea784a38f6bb23b51c83066eff1c5e222279ee (diff)
WIP
-rw-r--r--Makefile.am13
-rw-r--r--configure.ac12
-rw-r--r--src/facebook-connection.c446
3 files changed, 446 insertions, 25 deletions
diff --git a/Makefile.am b/Makefile.am
index 3f94376..8302211 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,13 +1,13 @@
AM_CFLAGS = -Wall -Werror -Wmissing-prototypes
+AM_CPPFLAGS = -DLIBEXECDIR=\"$(libexecdir)\"
ACLOCAL_AMFLAGS = -I m4
telepathy_libdir=$(libdir)/telepathy
telepathy_lib_PROGRAMS = src/telepathy-gruschler
-noinst_PROGRAMS = tests/test-facebook
-
-src_telepathy_gruschler_CFLAGS = $(AM_CFLAGS) $(REST_CFLAGS) $(TELEPATHY_CFLAGS)
-src_telepathy_gruschler_LDADD = $(AM_LDFLAGS) $(REST_LIBS) $(TELEPATHY_LIBS)
+libexec_PROGRAMS = src/telepathy-gruschler-login
+src_telepathy_gruschler_CFLAGS = $(AM_CFLAGS) $(GRUSCHLER_CFLAGS)
+src_telepathy_gruschler_LDADD = $(AM_LDFLAGS) $(GRUSCHLER_LIBS)
src_telepathy_gruschler_SOURCES = src/connection-manager.c \
src/connection-manager.h \
src/facebook-connection.c \
@@ -16,5 +16,6 @@ src_telepathy_gruschler_SOURCES = src/connection-manager.c \
src/facebook-contact-list.h \
src/main.c
-tests_test_facebook_CFLAGS = $(AM_CFLAGS) $(REST_CFLAGS)
-tests_test_facebook_LDADD = $(AM_LDFLAGS) $(REST_LIBS)
+src_telepathy_gruschler_login_CFLAGS = $(AM_CFLAGS) $(LOGIN_CFLAGS)
+src_telepathy_gruschler_login_LDADD = $(AM_LDFLAGS) $(LOGIN_LIBS)
+src_telepathy_gruschler_login_SOURCES = src/login.c
diff --git a/configure.ac b/configure.ac
index 4f00c57..d00d457 100644
--- a/configure.ac
+++ b/configure.ac
@@ -10,9 +10,15 @@ AM_PROG_CC_C_O
AC_PROG_LIBTOOL
AM_PATH_GLIB_2_0([2.20.3])
-PKG_CHECK_MODULES([TELEPATHY], [rtcom-telepathy-glib >= 0.1.38
- telepathy-glib >= 0.7.35])
-PKG_CHECK_MODULES([REST], [rest-extras-0.6 >= 0.6.1])
+PKG_CHECK_MODULES([GRUSCHLER],
+ [gio-unix-2.0 >= 2.20
+ json-glib-1.0 >= 0.8.0
+ rest-extras-0.6 >= 0.6.1
+ rtcom-telepathy-glib >= 0.1.38
+ telepathy-glib >= 0.7.35])
+PKG_CHECK_MODULES([LOGIN],
+ [gtkembedmoz >= 1.7
+ gtk+-2.0 >= 2.14])
FACEBOOK_APPKEY([GRUSCHLER])
diff --git a/src/facebook-connection.c b/src/facebook-connection.c
index 3d9273f..8883354 100644
--- a/src/facebook-connection.c
+++ b/src/facebook-connection.c
@@ -20,6 +20,8 @@
#include "facebook-contact-list.h"
+#include <gio/gunixinputstream.h>
+#include <json-glib/json-glib.h>
#include <libsoup/soup.h>
#include <rest-extras/facebook-proxy.h>
@@ -76,6 +78,10 @@ struct _GruschlerFacebookConnectionPrivate {
RestProxy *facebook;
SoupSession *session;
+ GPid login_helper_pid;
+ unsigned login_helper_id;
+ GInputStream * login_helper_stream;
+
TpHandleRepoIface *contacts;
TpHandleRepoIface *groups;
TpHandleRepoIface *lists;
@@ -284,6 +290,35 @@ _get_unique_connection_name (TpBaseConnection *connection)
return tp_escape_as_identifier (self->priv->email);
}
+static char *
+_get_cache_dir (GruschlerFacebookConnection *self)
+{
+ char *protocol, *unique_name, *filename;
+
+ g_object_get (self, "protocol", &protocol, NULL);
+ unique_name = _get_unique_connection_name (TP_BASE_CONNECTION (self));
+
+ filename = g_build_filename (g_get_user_cache_dir (), PACKAGE,
+ protocol, unique_name, NULL);
+
+ g_free (unique_name);
+ g_free (protocol);
+
+ return filename;
+}
+
+static char *
+_get_session_filename (GruschlerFacebookConnection *self)
+{
+ char *cache_dir, *filename;
+
+ cache_dir = _get_cache_dir (self);
+ filename = g_build_filename (cache_dir, "session", NULL);
+ g_free (cache_dir);
+
+ return filename;
+}
+
static const char *
_get_self_uid (GruschlerFacebookConnection *self)
{
@@ -347,6 +382,14 @@ _get_contact_status (GruschlerFacebookConnection *self,
}
static void
+_report_login_error (GruschlerFacebookConnection *self)
+{
+ tp_base_connection_change_status (TP_BASE_CONNECTION (self),
+ TP_CONNECTION_STATUS_DISCONNECTED,
+ TP_CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED);
+}
+
+static void
_report_network_error (GruschlerFacebookConnection *self)
{
tp_base_connection_change_status (TP_BASE_CONNECTION (self),
@@ -354,14 +397,108 @@ _report_network_error (GruschlerFacebookConnection *self)
TP_CONNECTION_STATUS_REASON_NETWORK_ERROR);
}
+static JsonNode *
+_get_json_root (RestProxyCall *call,
+ const GError *transport_error,
+ JsonNodeType expected_node_type,
+ GType expected_value_type)
+{
+ static JsonParser *parser = NULL;
+ JsonNode *root = NULL;
+ GError *error = NULL;
+ JsonObject *object;
+
+ if (transport_error)
+ {
+ g_warning ("Error from transport layer: %s", transport_error->message);
+ goto OUT;
+ }
+
+ if (!parser)
+ parser = json_parser_new ();
+
+const char *data = rest_proxy_call_get_payload (call);
+g_print ("data=%p\n", data);
+
+ if (!json_parser_load_from_data (parser,
+ rest_proxy_call_get_payload (call),
+ rest_proxy_call_get_payload_length (call),
+ &error))
+ {
+ g_warning ("%s: Invalid response from Facebook: %s",
+ G_STRLOC, error->message);
+
+ g_error_free (error);
+ goto OUT;
+ }
+
+ root = json_parser_get_root (parser);
+
+ switch (json_node_get_node_type (root))
+ {
+ case JSON_NODE_OBJECT:
+ object = json_node_get_object (root);
+
+ if (json_object_has_member (object, "error_msg"))
+ {
+ const char *msg = json_object_get_string_member (object, "error_msg");
+ gint64 code = json_object_get_int_member (object, "error_code");
+
+ g_warning ("%s: Error from facebook: %s (%" G_GINT64_FORMAT ")",
+ G_STRLOC, msg, code);
+
+ root = NULL;
+ goto OUT;
+ }
+
+ break;
+
+ case JSON_NODE_VALUE:
+ if (expected_value_type != json_node_get_value_type (root))
+ {
+ g_warning ("%s: Unexpected value type %s instead of %s",
+ G_STRLOC, g_type_name (json_node_get_value_type (root)),
+ g_type_name (expected_value_type));
+
+ root = NULL;
+ goto OUT;
+ }
+
+ break;
+
+ case JSON_NODE_ARRAY:
+ case JSON_NODE_NULL:
+ break;
+ }
+
+ if (expected_node_type != json_node_get_node_type (root))
+ {
+ g_warning ("%s: Unexpected node type %d instead of node type %d",
+ G_STRLOC, json_node_get_node_type (root), expected_node_type);
+
+ root = NULL;
+ goto OUT;
+ }
+
+OUT:
+ return root;
+}
+
static RestXmlNode *
_get_xml_root (RestProxyCall *call,
+ const GError *error,
const char *response_name)
{
static RestXmlParser *parser = NULL;
RestXmlNode *root, *node;
- if (parser == NULL)
+ if (error)
+ {
+ g_warning ("Error from transport layer: %s", error->message);
+ return NULL;
+ }
+
+ if (!parser)
parser = rest_xml_parser_new ();
root = rest_xml_parser_parse_from_data (parser,
@@ -371,13 +508,13 @@ _get_xml_root (RestProxyCall *call,
if (!g_strcmp0 (root->name, "error_response"))
{
node = rest_xml_node_find (root, "error_msg");
- g_warning ("error from facebook: %s", node->content);
+ g_warning ("Error from Facebook: %s", node->content);
rest_xml_node_unref (root);
root = NULL;
}
else if (g_strcmp0 (root->name, response_name))
{
- g_warning ("unexpected response from facebook: %s", root->name);
+ g_warning ("Unexpected response from Facebook: %s", root->name);
rest_xml_node_unref (root);
root = NULL;
}
@@ -499,7 +636,7 @@ _update_friends_cb (RestProxyCall *call,
RestXmlNode *root, *result, *name;
self = GRUSCHLER_FACEBOOK_CONNECTION (weak_object);
- root = _get_xml_root (call, "fql_multiquery_response");
+ root = _get_xml_root (call, error, "fql_multiquery_response");
if (!root)
{
@@ -577,6 +714,7 @@ g_debug ("queries=%s\n", queries->str);
g_object_unref (call);
}
+#if 0
static void
_auth_get_session_cb (RestProxyCall *call,
GError *error,
@@ -591,7 +729,7 @@ _auth_get_session_cb (RestProxyCall *call,
TpHandle handle;
self = GRUSCHLER_FACEBOOK_CONNECTION (weak_object);
- root = _get_xml_root (call, "auth_getSession_response");
+ root = _get_xml_root (call, error, "auth_getSession_response");
if (!root)
{
@@ -795,7 +933,7 @@ _auth_create_token_cb (RestProxyCall *call,
char *url;
self = GRUSCHLER_FACEBOOK_CONNECTION (weak_object);
- root = _get_xml_root (call, "auth_createToken_response");
+ root = _get_xml_root (call, error, "auth_createToken_response");
if (!root)
{
@@ -820,15 +958,231 @@ _auth_create_token_cb (RestProxyCall *call,
g_object_unref (message);
g_free (url);
}
+#endif
+
+static void
+_start_session (GruschlerFacebookConnection *self,
+ const char *session,
+ const char *secret,
+ const char *uid)
+{
+ TpHandle handle;
+
+ if (session)
+ {
+ g_debug ("session key: %s", session);
+ facebook_proxy_set_session_key (FACEBOOK_PROXY (self->priv->facebook), session);
+ }
+
+ if (secret)
+ {
+ g_debug ("session secret: %s", secret);
+ facebook_proxy_set_app_secret (FACEBOOK_PROXY (self->priv->facebook), secret);
+ }
+
+ if (uid)
+ {
+ g_debug ("session uid: %s", uid);
+
+ handle = tp_handle_ensure (self->priv->contacts, uid, NULL, NULL);
+ tp_base_connection_set_self_handle (TP_BASE_CONNECTION (self), handle);
+
+ tp_base_connection_change_status (TP_BASE_CONNECTION (self),
+ TP_CONNECTION_STATUS_CONNECTED,
+ TP_CONNECTION_STATUS_REASON_REQUESTED);
+
+ _update_friends (self);
+ }
+}
+
+static void
+_users_get_logged_in_user_cb (RestProxyCall *call,
+ GError *error,
+ GObject *weak_object,
+ gpointer user_data)
+{
+ GruschlerFacebookConnection *self;
+ JsonNode *root;
+ char *uid = NULL;
+
+ self = GRUSCHLER_FACEBOOK_CONNECTION (weak_object);
+ root = _get_json_root (call, error, JSON_NODE_VALUE, G_TYPE_INT64);
+
+ g_print ("%s\n", g_type_name (json_node_get_value_type (root)));
+ g_print ("%s\n", json_node_get_string (root));
+ g_print ("%" G_GINT64_FORMAT "\n", json_node_get_int (root));
+
+ if (root)
+ uid = g_strdup_printf ("%" G_GINT64_FORMAT, json_node_get_int (root));
+
+ if (uid)
+ {
+ g_debug ("resuming session for %s", uid);
+ _start_session (self, NULL, NULL, uid);
+ }
+
+ g_free (uid);
+}
+
+static gboolean
+_parse_session_string (const char *data,
+ gsize length,
+ char **session_out,
+ char **secret_out,
+ char **uid_out)
+{
+ GError *error = NULL;
+ JsonParser *parser = NULL;
+ JsonNode *root = NULL;
+ JsonObject *obj = NULL;
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (NULL != data, FALSE);
+
+ parser = json_parser_new ();
+
+ if (!json_parser_load_from_data (parser, data, length, &error))
+ {
+ g_warning ("%s: %s\n", G_STRLOC, error->message);
+ g_error_free (error);
+ goto OUT;
+ }
+
+ root = json_parser_get_root (parser);
+
+ if (root && JSON_NODE_OBJECT == json_node_get_node_type (root))
+ obj = json_node_get_object (root);
+
+ if (!obj ||
+ !json_object_has_member (obj, "session_key") ||
+ !json_object_has_member (obj, "secret") ||
+ !json_object_has_member (obj, "uid"))
+ {
+ g_warning ("%s: Invalid session string\n", G_STRLOC);
+ goto OUT;
+ }
+
+ if (session_out)
+ *session_out = g_strdup (json_object_get_string_member (obj, "session_key"));
+ if (session_out)
+ *secret_out = g_strdup (json_object_get_string_member (obj, "secret"));
+ if (uid_out)
+ *uid_out = g_strdup (json_object_get_string_member (obj, "uid"));
+
+ success = TRUE;
+
+OUT:
+ if (parser)
+ g_object_unref (parser);
+
+ return success;
+}
+
+static void
+_login_helper_watch_cb (GPid pid,
+ int status,
+ gpointer user_data)
+{
+ GruschlerFacebookConnection *self = user_data;
+ gboolean success = FALSE;
+ char buffer[1024];
+ const char *start, *end;
+ gsize length;
+
+ g_spawn_close_pid (pid);
+
+ g_return_if_fail (pid == self->priv->login_helper_pid);
+
+ if (g_input_stream_read_all (self->priv->login_helper_stream,
+ buffer, sizeof buffer, &length,
+ NULL, NULL))
+ {
+ if (!(start = g_strstr_len (buffer, length, "GRUSCHLER-SESSION:")))
+ goto OUT;
+
+ start += strlen ("GRUSCHLER-SESSION:");
+
+ for (end = start; end < buffer + length; ++end)
+ if ('\n' == *end)
+ break;
+
+ if ('\n' == *end)
+ {
+ char *key = NULL, *secret = NULL, *uid = NULL;
+
+ if (_parse_session_string (start, end - start, &key, &secret, &uid))
+ {
+ char *filename;
+
+ filename = _get_cache_dir (self);
+ g_mkdir_with_parents (filename, 0700);
+ g_free (filename);
+
+ filename = _get_session_filename (self);
+ g_file_set_contents (filename, start, end - start, NULL);
+ g_free (filename);
+
+ g_debug ("starting new session for %s", uid);
+ _start_session (self, key, secret, uid);
+
+ success = TRUE;
+ }
+
+ g_free (secret);
+ g_free (uid);
+ g_free (key);
+
+ goto OUT;
+ }
+ }
+
+OUT:
+ if (!success)
+ _report_login_error (self);
+
+ g_object_unref (self->priv->login_helper_stream);
+ self->priv->login_helper_stream = 0;
+ self->priv->login_helper_pid = 0;
+ self->priv->login_helper_id = 0;
+}
+
+static gboolean
+_run_login_helper (GruschlerFacebookConnection *self,
+ GError **error)
+{
+ char *argv[] = { LIBEXECDIR "/telepathy-gruschler-login", NULL };
+ int fd;
+
+ g_return_val_if_fail (0 == self->priv->login_helper_pid, FALSE);
+ g_return_val_if_fail (0 == self->priv->login_helper_id, FALSE);
+
+ g_debug ("running login helper");
+
+ if (!g_spawn_async_with_pipes (NULL, argv, NULL,
+ G_SPAWN_STDERR_TO_DEV_NULL |
+ G_SPAWN_DO_NOT_REAP_CHILD,
+ NULL, NULL, &self->priv->login_helper_pid,
+ NULL, &fd, NULL, error))
+ return FALSE;
+
+ self->priv->login_helper_id = g_child_watch_add (self->priv->login_helper_pid,
+ _login_helper_watch_cb, self);
+ self->priv->login_helper_stream = g_unix_input_stream_new (fd, TRUE);
+
+ return TRUE;
+}
static gboolean
_start_connecting (TpBaseConnection *connection,
GError **error_out)
{
GruschlerFacebookConnection *self;
- GError *error = NULL;
- RestProxyCall *call = NULL;
- gboolean success = FALSE;
+ GError *error = NULL;
+ RestProxyCall *call = NULL;
+ gboolean success = FALSE;
+ char *filename = NULL;
+ char *data = NULL;
+ gsize length;
self = GRUSCHLER_FACEBOOK_CONNECTION (connection);
@@ -839,24 +1193,50 @@ _start_connecting (TpBaseConnection *connection,
goto OUT;
}
- self->priv->facebook = facebook_proxy_new (GRUSCHLER_FACEBOOK_APIKEY,
- GRUSCHLER_FACEBOOK_SECRET);
+ self->priv->facebook = facebook_proxy_new (GRUSCHLER_FACEBOOK_APIKEY, NULL);
+// FIXME
+g_object_set (self->priv->facebook, "url-format", "http://api.facebook.com/restserver.php", NULL);
self->priv->session = soup_session_sync_new_with_options
(SOUP_SESSION_USER_AGENT, "Firefox/3.0 ",
SOUP_SESSION_ADD_FEATURE_BY_TYPE,
SOUP_TYPE_COOKIE_JAR, NULL);
- /* TODO: try to re-use last session key */
+ filename = _get_session_filename (self);
- call = rest_proxy_new_call (self->priv->facebook);
- rest_proxy_call_set_function (call, "auth.createToken");
- success = rest_proxy_call_async (call, _auth_create_token_cb,
- G_OBJECT (self), NULL, &error);
+ if (g_file_get_contents (filename, &data, &length, NULL))
+ {
+ char *key = NULL, *secret = NULL;
+
+ if (_parse_session_string (data, length, &key, &secret, NULL))
+ {
+ facebook_proxy_set_session_key (FACEBOOK_PROXY (self->priv->facebook), key);
+ facebook_proxy_set_app_secret (FACEBOOK_PROXY (self->priv->facebook), secret);
+g_print ("key=%s\n", key);
+g_print ("secret=%s\n", secret);
+
+ call = rest_proxy_new_call (self->priv->facebook);
+
+ rest_proxy_call_set_function (call, "users.getLoggedInUser");
+ rest_proxy_call_add_param (call, "format", "JSON");
+
+ success = rest_proxy_call_async (call, _users_get_logged_in_user_cb,
+ G_OBJECT (self), NULL, &error);
+ }
+
+ g_free (secret);
+ g_free (key);
+ }
+
+ if (!success)
+ success = _run_login_helper (self, &error);
OUT:
if (error)
{
+ g_warning ("%s: %s (%s:%d)", G_STRLOC, error->message,
+ g_quark_to_string (error->domain), error->code);
+
g_set_error (error_out, TP_ERRORS, TP_ERROR_NETWORK_ERROR, "%s (%s:%d)",
error->message, g_quark_to_string (error->domain),
error->code);
@@ -868,6 +1248,9 @@ OUT:
if (call)
g_object_unref (call);
+ g_free (filename);
+ g_free (data);
+
return success;
}
@@ -1281,6 +1664,36 @@ _get_property (GObject *object,
}
static void
+_dispose (GObject *object)
+{
+ GObjectClass *object_class;
+ GruschlerFacebookConnection *self;
+
+ self = GRUSCHLER_FACEBOOK_CONNECTION (object);
+
+ if (self->priv->login_helper_stream)
+ {
+ g_object_unref (self->priv->login_helper_stream);
+ self->priv->login_helper_stream = NULL;
+ }
+
+ if (self->priv->login_helper_id)
+ {
+ g_source_remove (self->priv->login_helper_id);
+ self->priv->login_helper_id = 0;
+ }
+
+ if (self->priv->login_helper_pid)
+ {
+ g_spawn_close_pid (self->priv->login_helper_pid);
+ self->priv->login_helper_pid = 0;
+ }
+
+ object_class = G_OBJECT_CLASS (gruschler_facebook_connection_parent_class);
+ object_class->dispose (object);
+}
+
+static void
_finalize (GObject *object)
{
GObjectClass *object_class;
@@ -1355,6 +1768,7 @@ gruschler_facebook_connection_class_init (GruschlerFacebookConnectionClass *clas
object_class->constructor = _constructor;
object_class->set_property = _set_property;
object_class->get_property = _get_property;
+ object_class->dispose = _dispose;
object_class->finalize = _finalize;
connection_class = TP_BASE_CONNECTION_CLASS (class);