diff options
author | Mathias Hasselmann <hasselmm@gnome.org> | 2009-11-06 10:37:20 +0100 |
---|---|---|
committer | Mathias Hasselmann <hasselmm@gnome.org> | 2009-11-06 10:37:20 +0100 |
commit | a94a7f46ec448c1415dc68203222c46e3f2a5741 (patch) | |
tree | fc3342620ba840152edc8afba069acbd497ef26e | |
parent | bcea784a38f6bb23b51c83066eff1c5e222279ee (diff) |
WIP
-rw-r--r-- | Makefile.am | 13 | ||||
-rw-r--r-- | configure.ac | 12 | ||||
-rw-r--r-- | src/facebook-connection.c | 446 |
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); |