diff options
author | Mathias Hasselmann <hasselmm@gnome.org> | 2009-10-19 22:40:21 +0200 |
---|---|---|
committer | Mathias Hasselmann <hasselmm@gnome.org> | 2009-10-20 21:19:54 +0200 |
commit | 324498ec6af10a73133c1ff8222658a2dbc46615 (patch) | |
tree | 2d4f592c64f3df0379939bdce4c15a88ee0c8b9a /src |
Initial commit
Diffstat (limited to 'src')
-rw-r--r-- | src/connection-manager.c | 93 | ||||
-rw-r--r-- | src/connection-manager.h | 53 | ||||
-rw-r--r-- | src/facebook-connection.c | 1101 | ||||
-rw-r--r-- | src/facebook-connection.h | 67 | ||||
-rw-r--r-- | src/facebook-contact-list.c | 508 | ||||
-rw-r--r-- | src/facebook-contact-list.h | 62 | ||||
-rw-r--r-- | src/main.c | 35 |
7 files changed, 1919 insertions, 0 deletions
diff --git a/src/connection-manager.c b/src/connection-manager.c new file mode 100644 index 0000000..824ea2e --- /dev/null +++ b/src/connection-manager.c @@ -0,0 +1,93 @@ +/* telepathy-gruschler - A Telepathy connection manager for social networks. + * Copyright (C) 2009 Mathias Hasselmann + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "config.h" +#include "connection-manager.h" +#include "facebook-connection.h" + +G_DEFINE_TYPE (GruschlerConnectionManager, + gruschler_connection_manager, + TP_TYPE_BASE_CONNECTION_MANAGER); + +static const TpCMParamSpec gruschler_facebook_connection_params[] = { + { "email", "s", G_TYPE_STRING, + TP_CONN_MGR_PARAM_FLAG_REQUIRED | + TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT, + GRUSCHLER_FACEBOOK_DEFAULT_EMAIL, + G_STRUCT_OFFSET (GruschlerFacebookConnectionParams, email), + NULL, NULL, NULL }, + + { "password", "s", G_TYPE_STRING, + TP_CONN_MGR_PARAM_FLAG_REQUIRED | + TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT | + TP_CONN_MGR_PARAM_FLAG_SECRET, + GRUSCHLER_FACEBOOK_DEFAULT_PASSWORD, + G_STRUCT_OFFSET (GruschlerFacebookConnectionParams, password), + NULL, NULL, NULL }, + + { NULL, } +}; + +static void +gruschler_connection_manager_init (GruschlerConnectionManager *self) +{ +} + +static TpBaseConnection * +gruschler_connection_manager_new_connection (TpBaseConnectionManager *self, + const char *proto, + TpIntSet *params_present, + void *parsed_params, + GError **error) +{ + if (!g_strcmp0 (proto, "facebook")) + return gruschler_facebook_connection_new (self, parsed_params, error); + + g_set_error (error, TP_ERRORS, + TP_ERROR_INVALID_ARGUMENT, + "unknown protocol name %s", proto); + + return NULL; +} + +static void +gruschler_connection_manager_class_init (GruschlerConnectionManagerClass *class) +{ + static const TpCMProtocolSpec protocol_params[] = { + { "facebook", + gruschler_facebook_connection_params, + gruschler_facebook_connection_params_new, + gruschler_facebook_connection_params_free, NULL + }, + + { NULL, } + }; + + TpBaseConnectionManagerClass *base_cm_class; + + base_cm_class = TP_BASE_CONNECTION_MANAGER_CLASS (class); + base_cm_class->cm_dbus_name = "gruschler"; + base_cm_class->protocol_params = protocol_params; + base_cm_class->new_connection = gruschler_connection_manager_new_connection; +} + +TpBaseConnectionManager * +gruschler_connection_manager_new (void) +{ + return g_object_new (GRUSCHLER_TYPE_CONNECTION_MANAGER, NULL); +} + diff --git a/src/connection-manager.h b/src/connection-manager.h new file mode 100644 index 0000000..3609fbd --- /dev/null +++ b/src/connection-manager.h @@ -0,0 +1,53 @@ +/* telepathy-gruschler - A Telepathy connection manager for social networks. + * Copyright (C) 2009 Mathias Hasselmann + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __GRUSCHLER_CONNECTION_MANAGER_H__ +#define __GRUSCHLER_CONNECTION_MANAGER_H__ + +#include <telepathy-glib/base-connection-manager.h> + +G_BEGIN_DECLS + +#define GRUSCHLER_TYPE_CONNECTION_MANAGER (gruschler_connection_manager_get_type ()) +#define GRUSCHLER_CONNECTION_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GRUSCHLER_TYPE_CONNECTION_MANAGER, GruschlerConnectionManager)) +#define GRUSCHLER_CONNECTION_MANAGER_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST ((cls), GRUSCHLER_TYPE_CONNECTION_MANAGER, GruschlerConnectionManagerClass)) +#define GRUSCHLER_IS_CONNECTION_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GRUSCHLER_TYPE_CONNECTION_MANAGER)) +#define GRUSCHLER_IS_CONNECTION_MANAGER_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE ((obj), GRUSCHLER_TYPE_CONNECTION_MANAGER)) +#define GRUSCHLER_CONNECTION_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GRUSCHLER_TYPE_CONNECTION_MANAGER, GruschlerConnectionManagerClass)) + +typedef struct _GruschlerConnectionManager GruschlerConnectionManager; +typedef struct _GruschlerConnectionManagerClass GruschlerConnectionManagerClass; +typedef struct _GruschlerConnectionManagerPrivate GruschlerConnectionManagerPrivate; + +struct _GruschlerConnectionManager { + TpBaseConnectionManager parent_instance; + GruschlerConnectionManagerPrivate *priv; +}; + +struct _GruschlerConnectionManagerClass { + TpBaseConnectionManagerClass parent_class; +}; + +GType +gruschler_connection_manager_get_type (void) G_GNUC_CONST; + +TpBaseConnectionManager * +gruschler_connection_manager_new (void); + +G_END_DECLS + +#endif /* __GRUSCHLER_CONNECTION_MANAGER_H__ */ diff --git a/src/facebook-connection.c b/src/facebook-connection.c new file mode 100644 index 0000000..d0fc959 --- /dev/null +++ b/src/facebook-connection.c @@ -0,0 +1,1101 @@ +/* telepathy-gruschler - A Telepathy connection manager for social networks. + * Copyright (C) 2009 Mathias Hasselmann + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "config.h" +#include "facebook-connection.h" + +#include "facebook-contact-list.h" + +#include <libsoup/soup.h> + +#include <rest-extras/facebook-proxy.h> +#include <rest/rest-xml-parser.h> + +#include <rtcom-telepathy-glib/extensions.h> + +#include <telepathy-glib/dbus.h> +#include <telepathy-glib/handle-repo-dynamic.h> +#include <telepathy-glib/handle-repo-static.h> +#include <telepathy-glib/interfaces.h> + +#include <string.h> + +enum { + PROP_0, + PROP_EMAIL, + PROP_PASSWORD, +}; + +static const char *const _fixed_properties[] = { + TP_IFACE_CHANNEL ".ChannelType", + TP_IFACE_CHANNEL ".TargetHandleType", + NULL +}; + +static const char *const _allowed_properties[] = { + TP_IFACE_CHANNEL ".TargetHandle", + TP_IFACE_CHANNEL ".TargetID", + NULL +}; + +struct _GruschlerFacebookConnectionPrivate { + RestProxy *facebook; + SoupSession *session; + + TpHandleRepoIface *contacts; + TpHandleRepoIface *groups; + TpHandleRepoIface *lists; + + GHashTable *group_channels; + GHashTable *list_channels; + + char *email; + char *password; + char *token; +}; + +static void +_channel_manager_iface_init (TpChannelManagerIface *iface); + +G_DEFINE_TYPE_WITH_CODE (GruschlerFacebookConnection, + gruschler_facebook_connection, + TP_TYPE_BASE_CONNECTION, + + G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER, + _channel_manager_iface_init); +); + +static void +_set_property (GObject *object, + unsigned prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GruschlerFacebookConnection *self; + + self = GRUSCHLER_FACEBOOK_CONNECTION (object); + + switch (prop_id) + { + case PROP_EMAIL: + g_free (self->priv->email); + self->priv->email = g_value_dup_string (value); + break; + + case PROP_PASSWORD: + g_free (self->priv->password); + self->priv->password = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +_get_property (GObject *object, + unsigned prop_id, + GValue *value, + GParamSpec *pspec) +{ + GruschlerFacebookConnection *self; + + self = GRUSCHLER_FACEBOOK_CONNECTION (object); + + switch (prop_id) + { + case PROP_EMAIL: + g_value_set_string (value, self->priv->email); + break; + + case PROP_PASSWORD: + g_value_set_string (value, self->priv->password); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +_finalize (GObject *object) +{ + GObjectClass *object_class; + GruschlerFacebookConnection *self; + + self = GRUSCHLER_FACEBOOK_CONNECTION (object); + + if (self->priv->facebook) + g_object_unref (self->priv->facebook); + if (self->priv->session) + g_object_unref (self->priv->session); + if (self->priv->contacts) + g_object_unref (self->priv->contacts); + if (self->priv->groups) + g_object_unref (self->priv->groups); + if (self->priv->lists) + g_object_unref (self->priv->lists); + if (self->priv->list_channels) + g_hash_table_unref (self->priv->list_channels); + if (self->priv->group_channels) + g_hash_table_unref (self->priv->group_channels); + + g_free (self->priv->email); + g_free (self->priv->password); + g_free (self->priv->token); + + object_class = G_OBJECT_CLASS (gruschler_facebook_connection_parent_class); + object_class->finalize (object); +} + +static void +_create_handle_repos (TpBaseConnection *connection, + TpHandleRepoIface *repos[NUM_TP_HANDLE_TYPES]) +{ + static const char *list_handle_names[] = { + "publish", /* GRUSCHLER_FACEBOOK_LIST_HANDLE_PUBLISH */ + "subscribe", /* GRUSCHLER_FACEBOOK_LIST_HANDLE_SUBSCRIBE */ + "stored", /* GRUSCHLER_FACEBOOK_LIST_HANDLE_STORED */ + "deny", /* GRUSCHLER_FACEBOOK_LIST_HANDLE_DENY */ + NULL + }; + + GruschlerFacebookConnection *self; + + self = GRUSCHLER_FACEBOOK_CONNECTION (connection); + + self->priv->contacts = tp_dynamic_handle_repo_new (TP_HANDLE_TYPE_CONTACT, NULL, NULL); + self->priv->groups = tp_dynamic_handle_repo_new (TP_HANDLE_TYPE_GROUP, NULL, NULL); + self->priv->lists = tp_static_handle_repo_new (TP_HANDLE_TYPE_LIST, list_handle_names); + + repos[TP_HANDLE_TYPE_CONTACT] = self->priv->contacts; + repos[TP_HANDLE_TYPE_GROUP] = self->priv->groups; + repos[TP_HANDLE_TYPE_LIST] = self->priv->lists; +} + +static GPtrArray * +_create_channel_managers (TpBaseConnection *connection) +{ + GPtrArray *managers = g_ptr_array_new (); + g_ptr_array_add (managers, connection); + return managers; +} + +static GHashTable * +_get_channel_map (GruschlerFacebookConnection *self, + TpHandleType handle_type) +{ + switch (handle_type) + { + case TP_HANDLE_TYPE_LIST: + return self->priv->list_channels; + case TP_HANDLE_TYPE_GROUP: + return self->priv->group_channels; + default: + break; + } + + g_warning ("%s: invalid handle type %s", + G_STRLOC, tp_handle_type_to_string (handle_type)); + + return NULL; +} + +static void +_contact_list_closed_cb (TpExportableChannel *channel, + gpointer user_data) +{ + GruschlerFacebookConnection *self; + TpChannelManager *manager = user_data; + gboolean destroyed; + TpHandleType handle_type; + TpHandle handle; + + self = GRUSCHLER_FACEBOOK_CONNECTION (manager); + + tp_channel_manager_emit_channel_closed_for_object (manager, channel); + + g_object_get (channel, + "handle", &handle, + "handle-type", &handle_type, + "channel-destroyed", &destroyed, NULL); + + if (destroyed) + { + g_debug ("removing channel with %s %u", + tp_handle_type_to_string (handle_type), handle); + + g_hash_table_remove (_get_channel_map (self, handle_type), + GUINT_TO_POINTER (handle)); + } + else + { + g_debug ("reopening channel with handle %u", handle); + tp_channel_manager_emit_new_channel (manager, channel, NULL); + } +} + +static TpExportableChannel * +_create_contact_list (GruschlerFacebookConnection *self, + TpHandleType handle_type, + TpHandle handle, + gpointer request_token) +{ + TpExportableChannel *channel; + GHashTable *channel_map; + char *object_path; + char *mangled_name; + const char *handle_name; + GSList *requests = NULL; + TpHandleRepoIface *handle_repo; + + channel_map = _get_channel_map (self, handle_type); + + g_assert (!g_hash_table_lookup (channel_map, GUINT_TO_POINTER (handle))); + + handle_repo = tp_base_connection_get_handles (TP_BASE_CONNECTION (self), handle_type); + handle_name = tp_handle_inspect (handle_repo, handle); + mangled_name = tp_escape_as_identifier (handle_name); + + g_debug ("instantiating channel %s:%u \"%s\"", + tp_handle_type_to_string (handle_type), + handle, handle_name); + + object_path = g_strdup_printf ("%s/ContactList/%s/%s", + TP_BASE_CONNECTION (self)->object_path, + TP_HANDLE_TYPE_LIST == handle_type ? "List" + : "Group", + mangled_name); + + channel = g_object_new (GRUSCHLER_TYPE_FACEBOOK_CONTACT_LIST, + "connection", self, + "object-path", object_path, + "handle", handle, + "handle-type", handle_type, + NULL); + + g_debug ("created %s", object_path); + + g_hash_table_insert (channel_map, GUINT_TO_POINTER (handle), channel); + + if (request_token) + requests = g_slist_prepend (requests, request_token); + +/* TODO: check if list received */ + tp_channel_manager_emit_new_channel (TP_CHANNEL_MANAGER (self), channel, requests); + + g_signal_connect (channel, "closed", + G_CALLBACK (_contact_list_closed_cb), self); + + g_slist_free (requests); + g_free (mangled_name); + g_free (object_path); + + return channel; +} + +static TpExportableChannel * +_ensure_contact_list (GruschlerFacebookConnection *self, + TpHandleType handle_type, + TpHandle handle, + gpointer request_token) +{ + TpExportableChannel *channel; + + channel = g_hash_table_lookup (_get_channel_map (self, handle_type), + GUINT_TO_POINTER (handle)); + + if (!channel) + channel = _create_contact_list (self, handle_type, handle, request_token); + + return channel; +} + +static char * +_get_unique_connection_name (TpBaseConnection *connection) +{ + GruschlerFacebookConnection *self; + self = GRUSCHLER_FACEBOOK_CONNECTION (connection); + return tp_escape_as_identifier (self->priv->email); +} + +const char * +_get_uid (GruschlerFacebookConnection *self) +{ + return tp_handle_inspect (self->priv->contacts, + TP_BASE_CONNECTION (self)->self_handle); +} + +static void +_report_network_error (GruschlerFacebookConnection *self) +{ + tp_base_connection_change_status (TP_BASE_CONNECTION (self), + TP_CONNECTION_STATUS_DISCONNECTED, + TP_CONNECTION_STATUS_REASON_NETWORK_ERROR); +} + +static RestXmlNode * +_get_xml_root (RestProxyCall *call, + const char *response_name) +{ + static RestXmlParser *parser = NULL; + RestXmlNode *root, *node; + + if (parser == NULL) + parser = rest_xml_parser_new (); + + root = rest_xml_parser_parse_from_data (parser, + rest_proxy_call_get_payload (call), + rest_proxy_call_get_payload_length (call)); + + if (!g_strcmp0 (root->name, "error_response")) + { + node = rest_xml_node_find (root, "error_msg"); + 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); + rest_xml_node_unref (root); + root = NULL; + } + + return root; +} + +static void +_update_uids (GruschlerFacebookConnection *self, + RestXmlNode *result) +{ + TpIntSet *added, *removed; + RestXmlNode *node, *uid; + TpExportableChannel *channel; + TpHandle handle; + + added = tp_intset_new (); + removed = tp_intset_new (); + + for (node = rest_xml_node_find (result, "friend_info"); + node; node = node->next) + { + if (g_strcmp0 (node->name, "friend_info")) + continue; + + uid = rest_xml_node_find (node, "uid2"); + + if (!uid) + continue; + + handle = tp_handle_ensure (self->priv->contacts, + uid->content, NULL, NULL); + + /* TODO: really check changes */ + tp_intset_add (added, handle); + } + + channel = _ensure_contact_list (self, TP_HANDLE_TYPE_LIST, + GRUSCHLER_FACEBOOK_LIST_HANDLE_PUBLISH, NULL); + + tp_group_mixin_change_members (G_OBJECT (channel), NULL, + added, removed, NULL, NULL, + TP_BASE_CONNECTION (self)->self_handle, + TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + + channel = _ensure_contact_list (self, TP_HANDLE_TYPE_LIST, + GRUSCHLER_FACEBOOK_LIST_HANDLE_SUBSCRIBE, NULL); + + tp_group_mixin_change_members (G_OBJECT (channel), NULL, + added, removed, NULL, NULL, + TP_BASE_CONNECTION (self)->self_handle, + TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + + channel = _ensure_contact_list (self, TP_HANDLE_TYPE_LIST, + GRUSCHLER_FACEBOOK_LIST_HANDLE_STORED, NULL); + + tp_group_mixin_change_members (G_OBJECT (channel), NULL, + added, removed, NULL, NULL, + TP_BASE_CONNECTION (self)->self_handle, + TP_CHANNEL_GROUP_CHANGE_REASON_NONE); + + tp_intset_destroy (removed); + tp_intset_destroy (added); +} + +static void +_update_profiles (GruschlerFacebookConnection *self, + RestXmlNode *result) +{ + /* TODO: read profiles to make aliasing interface work */ +} + +static void +_update_friends_cb (RestProxyCall *call, + GError *error, + GObject *weak_object, + gpointer user_data) +{ + GruschlerFacebookConnection *self; + RestXmlNode *root, *result, *name; + + self = GRUSCHLER_FACEBOOK_CONNECTION (weak_object); + root = _get_xml_root (call, "fql_multiquery_response"); + + if (!root) + { + _report_network_error (self); + return; + } + + for (result = rest_xml_node_find (root, "fql_result"); + result; result = result->next) + { + name = rest_xml_node_find (result, "name"); + + if (!name) + continue; + + if (!g_strcmp0 (name->content, "uids")) + { + _update_uids (self, result); + continue; + } + + if (!g_strcmp0 (name->content, "profiles")) + { + _update_profiles (self, result); + continue; + } + } + + rest_xml_node_unref (root); +} + +static void +_update_friends (GruschlerFacebookConnection *self) +{ + GError *error = NULL; + GString *queries; + RestProxyCall *call; + + queries = g_string_new (NULL); + + g_string_append_c (queries, '{'); + + g_string_append_printf (queries, + "\"uids\":" + "\"SELECT uid2 FROM friend WHERE uid1=%s\"", + _get_uid (self)); + + g_string_append_c (queries, ','); + + g_string_append_printf (queries, + "\"profiles\":" + "\"SELECT uid,name,profile_update_time " + "FROM user WHERE uid IN (SELECT uid2 FROM friend WHERE uid1=%s)\"", + _get_uid (self)); + /* TODO: "AND profile_update_time > 0" */ + + g_string_append_c (queries, '}'); + +g_debug ("queries=%s\n", queries->str); + + call = rest_proxy_new_call (self->priv->facebook); + rest_proxy_call_set_function (call, "fql.multiquery"); + rest_proxy_call_add_param (call, "auth_token", self->priv->token); + rest_proxy_call_add_param (call, "queries", queries->str); + + if (! rest_proxy_call_async (call, _update_friends_cb, + G_OBJECT (self), NULL, &error)) + { + /* TODO: properly handle errors */ + g_warning ("%s: %s", G_STRLOC, error->message); + g_error_free (error); + } + + g_string_free (queries, TRUE); + g_object_unref (call); +} + +static void +_auth_get_session_cb (RestProxyCall *call, + GError *error, + GObject *weak_object, + gpointer user_data) +{ + GruschlerFacebookConnection *self; + const char *session_key = NULL; + const char *secret = NULL; + const char *uid = NULL; + RestXmlNode *root, *node; + TpHandle handle; + + self = GRUSCHLER_FACEBOOK_CONNECTION (weak_object); + root = _get_xml_root (call, "auth_getSession_response"); + + if (!root) + { + _report_network_error (self); + return; + } + + if (NULL != (node = rest_xml_node_find (root, "session_key"))) + session_key = node->content; + if (NULL != (node = rest_xml_node_find (root, "secret"))) + secret = node->content; + if (NULL != (node = rest_xml_node_find (root, "uid"))) + uid = node->content; + + if (!session_key || !secret || !uid) + { + _report_network_error (self); + return; + } + + g_debug ("got new secret %s and session key %s for %s\n", secret, session_key, uid); + facebook_proxy_set_session_key (FACEBOOK_PROXY (self->priv->facebook), session_key); + facebook_proxy_set_app_secret (FACEBOOK_PROXY (self->priv->facebook), secret); + + 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); + + rest_xml_node_unref (root); + _update_friends (self); +} + +static void +_post_login_data_cb (SoupSession *session, + SoupMessage *message, + gpointer user_data) +{ + GruschlerFacebookConnection *self = user_data; + GError *error = NULL; + RestProxyCall *call; + SoupURI *uri; + + uri = soup_message_get_uri (message); + +g_debug ("status=%d length=%lld", message->status_code, message->response_body->length); +g_debug ("%s %s", uri->path, uri->query); + + if (!g_strcmp0 (uri->path, "/login.php")) + { + tp_base_connection_change_status (TP_BASE_CONNECTION (self), + TP_CONNECTION_STATUS_DISCONNECTED, + TP_CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED); + } + else if (!g_strcmp0 (uri->path, "/desktopapp.php") && + !g_strcmp0 (uri->query, "api_key=" GRUSCHLER_FACEBOOK_APIKEY)) + { + call = rest_proxy_new_call (self->priv->facebook); + rest_proxy_call_set_function (call, "auth.getSession"); + rest_proxy_call_add_param (call, "auth_token", self->priv->token); + rest_proxy_call_async (call, _auth_get_session_cb, + G_OBJECT (self), NULL, &error); + /* TODO: handle error */ + } + else + _report_network_error (self); +} + +static GHashTable * +_parse_form (const char *data, + gsize length) +{ + GRegex *regex_field, *regex_attrs; + GMatchInfo *match_field, *match_attrs; + GHashTable *params; + + params = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + regex_field = g_regex_new ("<input\\s+(.*?)>", G_REGEX_CASELESS | G_REGEX_DOTALL, 0, NULL); + regex_attrs = g_regex_new ("\\b(\\S+?)=\"(.*?)\"", G_REGEX_CASELESS | G_REGEX_DOTALL, 0, NULL); + + if (g_regex_match_full (regex_field, data, length, 0, 0, &match_field, NULL)) + { + do + { + char *name = NULL; + char *value = NULL; + int start, end; + + g_match_info_fetch_pos (match_field, 1, &start, &end); + + if (g_regex_match_full (regex_attrs, data + start, + end - start, 0, 0, &match_attrs, NULL)) + { + do + { + char *param = g_match_info_fetch (match_attrs, 1); + + if (!g_strcmp0 (param, "name")) + name = g_match_info_fetch (match_attrs, 2); + else if (!g_strcmp0 (param, "value")) + value = g_match_info_fetch (match_attrs, 2); + + g_free (param); + } + while (g_match_info_next (match_attrs, NULL)); + } + + g_match_info_free (match_attrs); + + if (!name || !value) + { + g_free (value); + g_free (name); + continue; + } + + g_hash_table_insert (params, name, value); + } + while (g_match_info_next (match_field, NULL)); + } + + g_match_info_free (match_field); + g_regex_unref (regex_attrs); + g_regex_unref (regex_field); + + return params; +} + +static void +_fetch_login_form_cb (SoupSession *session, + SoupMessage *message, + gpointer user_data) +{ + GruschlerFacebookConnection *self = user_data; + char *form = NULL; + char *url = NULL; + int start, end; + GHashTable *params; + GRegex *regex; + GMatchInfo *match; + + if (SOUP_STATUS_OK != message->status_code) + { + g_warning ("cannot fetch login form: %s", message->reason_phrase); + _report_network_error (self); + return; + } + + regex = g_regex_new ("<form.*?action=\"(.*?)\".*?>(.*?)</form>", + G_REGEX_CASELESS | G_REGEX_DOTALL, 0, NULL); + + if (g_regex_match_full (regex, message->response_body->data, + message->response_body->length, 0, 0, &match, NULL)) + { + url = g_match_info_fetch (match, 1); + g_match_info_fetch_pos (match, 2, &start, &end); + } + + g_match_info_free (match); + g_regex_unref (regex); + + if (!url) + { + g_warning ("cannot find login URL"); + _report_network_error (self); + return; + } + + params = _parse_form (message->response_body->data + start, end - start); + g_hash_table_insert (params, g_strdup ("email"), g_strdup (self->priv->email)); + g_hash_table_insert (params, g_strdup ("pass"), g_strdup (self->priv->password)); + g_hash_table_remove (params, "charset_test"); + form = soup_form_encode_hash (params); + g_hash_table_unref (params); + + message = soup_message_new (SOUP_METHOD_POST, url); + + soup_message_set_request (message, "application/x-www-form-urlencoded", + SOUP_MEMORY_TAKE, form, strlen (form)); + soup_session_queue_message (self->priv->session, message, + _post_login_data_cb, self); + + g_object_unref (message); + g_free (url); +} + +static void +_auth_create_token_cb (RestProxyCall *call, + GError *error, + GObject *weak_object, + gpointer user_data) +{ + GruschlerFacebookConnection *self; + RestXmlNode *root; + SoupMessage *message; + char *url; + + self = GRUSCHLER_FACEBOOK_CONNECTION (weak_object); + root = _get_xml_root (call, "auth_createToken_response"); + + if (!root) + { + _report_network_error (self); + return; + } + + g_free (self->priv->token); + self->priv->token = g_strdup (root->content); + rest_xml_node_unref (root); + + g_debug ("login token is %s", self->priv->token); + + url = facebook_proxy_build_login_url (FACEBOOK_PROXY (self->priv->facebook), + self->priv->token); + + message = soup_message_new (SOUP_METHOD_GET, url); + + soup_session_queue_message (self->priv->session, message, + _fetch_login_form_cb, self); + + g_object_unref (message); + g_free (url); +} + +static gboolean +_start_connecting (TpBaseConnection *connection, + GError **error_out) +{ + GruschlerFacebookConnection *self; + GError *error = NULL; + RestProxyCall *call = NULL; + gboolean success = FALSE; + + self = GRUSCHLER_FACEBOOK_CONNECTION (connection); + + if (self->priv->facebook || self->priv->session) + { + g_set_error_literal (error_out, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "invalid state: session already exists"); + goto OUT; + } + + self->priv->facebook = facebook_proxy_new (GRUSCHLER_FACEBOOK_APIKEY, + GRUSCHLER_FACEBOOK_SECRET); + + 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 */ + + 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); + +OUT: + if (error) + { + g_set_error (error_out, TP_ERRORS, TP_ERROR_NETWORK_ERROR, "%s (%s:%d)", + error->message, g_quark_to_string (error->domain), + error->code); + g_error_free (error); + } + else if (!success) + g_assert (!error_out || *error_out); + + if (call) + g_object_unref (call); + + return success; +} + +static void +_shut_down (TpBaseConnection *connection) +{ + GruschlerFacebookConnection *self; + + self = GRUSCHLER_FACEBOOK_CONNECTION (connection); + + if (self->priv->facebook) + { + g_object_unref (self->priv->facebook); + self->priv->facebook = NULL; + } +} + +static void +gruschler_facebook_connection_class_init (GruschlerFacebookConnectionClass *class) +{ + static const char *interfaces[] = { +#if 0 + TP_IFACE_CONNECTION_INTERFACE_ALIASING, + TP_IFACE_CONNECTION_INTERFACE_AVATARS, + TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE, + TP_IFACE_CONNECTION_INTERFACE_CONTACTS, + TP_IFACE_CONNECTION_INTERFACE_LOCATION, + RTCOM_TP_IFACE_CONNECTION_INTERFACE_CONTACT_INFO, +#endif + NULL + }; + + GParamSpec *pspec; + GObjectClass *object_class; + TpBaseConnectionClass *connection_class; + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = _set_property; + object_class->get_property = _get_property; + object_class->finalize = _finalize; + + connection_class = TP_BASE_CONNECTION_CLASS (class); + connection_class->create_handle_repos = _create_handle_repos; + connection_class->create_channel_managers = _create_channel_managers; + connection_class->get_unique_connection_name = _get_unique_connection_name; + connection_class->start_connecting = _start_connecting; + connection_class->shut_down = _shut_down; + connection_class->interfaces_always_present = interfaces; + + pspec = g_param_spec_string ("email", + "Email", + "Email address for accessing Facebook", + GRUSCHLER_FACEBOOK_DEFAULT_EMAIL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | + G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_property (object_class, PROP_EMAIL, pspec); + + pspec = g_param_spec_string ("password", + "Password", + "Password for accessing Facebook", + GRUSCHLER_FACEBOOK_DEFAULT_PASSWORD, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | + G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_property (object_class, PROP_PASSWORD, pspec); + + g_type_class_add_private (class, sizeof (GruschlerFacebookConnectionPrivate)); +} + +static void +_foreach_channel (TpChannelManager *manager, + TpExportableChannelFunc callback, + gpointer user_data) +{ + GruschlerFacebookConnection *self; + GHashTableIter iter; + TpExportableChannel *channel; + + self = GRUSCHLER_FACEBOOK_CONNECTION (manager); + + g_return_if_fail (NULL != self->priv->list_channels); + g_return_if_fail (NULL != self->priv->group_channels); + + g_hash_table_iter_init (&iter, self->priv->list_channels); + + while (g_hash_table_iter_next (&iter, NULL, (gpointer) &channel)) + callback (channel, user_data); + + g_hash_table_iter_init (&iter, self->priv->group_channels); + + while (g_hash_table_iter_next (&iter, NULL, (gpointer) &channel)) + callback (channel, user_data); +} + +static void +_foreach_channel_class (TpChannelManager *manager, + TpChannelManagerChannelClassFunc callback, + gpointer user_data) +{ + GHashTable *fixed; + + fixed = tp_asv_new (_fixed_properties[0], G_TYPE_STRING, + TP_IFACE_CHANNEL_TYPE_CONTACT_LIST, + _fixed_properties[1], G_TYPE_UINT, + TP_HANDLE_TYPE_LIST, NULL); + + callback (manager, fixed, _allowed_properties, user_data); + + tp_asv_set_uint32 (fixed, _fixed_properties[1], + TP_HANDLE_TYPE_GROUP); + + callback (manager, fixed, _allowed_properties, user_data); + + g_hash_table_unref (fixed); +} + +static gboolean +_handle_channel_request (TpChannelManager *manager, + gpointer request_token, + GHashTable *request_properties, + gboolean require_new) +{ + GruschlerFacebookConnection *self; + GError *error = NULL; + TpExportableChannel *channel = NULL; + const char *channel_type; + TpHandleType handle_type; + TpHandleRepoIface *handle_repo; + TpHandle handle; + + self = GRUSCHLER_FACEBOOK_CONNECTION (manager); + + channel_type = tp_asv_get_string (request_properties, TP_IFACE_CHANNEL ".ChannelType"); + handle_type = tp_asv_get_uint32 (request_properties, TP_IFACE_CHANNEL ".TargetHandleType", NULL); + + if (handle_type != TP_HANDLE_TYPE_LIST && + handle_type != TP_HANDLE_TYPE_GROUP) + return FALSE; + if (g_strcmp0 (channel_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST)) + return FALSE; + + /* Check if the handle is valid */ + handle = tp_asv_get_uint32 (request_properties, TP_IFACE_CHANNEL ".TargetHandle", NULL); + handle_repo = tp_base_connection_get_handles (TP_BASE_CONNECTION (self), handle_type); + + if (!tp_handle_is_valid (handle_repo, handle, &error)) + goto OUT; + + /* Check if there are any other properties that we don't understand */ + if (tp_channel_manager_asv_has_unknown_properties (request_properties, + _fixed_properties, + _allowed_properties, + &error)) + goto OUT; + + channel = g_hash_table_lookup (_get_channel_map (self, handle_type), + GUINT_TO_POINTER (handle)); + + if (!channel) + { + _create_contact_list (self, handle_type, handle, request_token); + return TRUE; + } + + if (require_new) + g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "Contact list #%u has been created already", handle); + +OUT: + if (channel) + tp_channel_manager_emit_request_already_satisfied (self, request_token, channel); + else + { + tp_channel_manager_emit_request_failed (self, request_token, error->domain, + error->code, error->message); + g_error_free (error); + } + + return TRUE; +} + +static gboolean +_create_channel (TpChannelManager *manager, + gpointer request_token, + GHashTable *request_properties) +{ + return _handle_channel_request (manager, request_token, request_properties, TRUE); +} + +static gboolean +_request_channel (TpChannelManager *manager, + gpointer request_token, + GHashTable *request_properties) +{ + return _handle_channel_request (manager, request_token, request_properties, FALSE); +} + +static gboolean +_ensure_channel (TpChannelManager *manager, + gpointer request_token, + GHashTable *request_properties) +{ + return _handle_channel_request (manager, request_token, request_properties, FALSE); +} + +static void +_channel_manager_iface_init (TpChannelManagerIface *iface) +{ + iface->foreach_channel = _foreach_channel; + iface->foreach_channel_class = _foreach_channel_class; + iface->create_channel = _create_channel; + iface->request_channel = _request_channel; + iface->ensure_channel = _ensure_channel; +} + +static void +gruschler_facebook_connection_init (GruschlerFacebookConnection *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + GRUSCHLER_TYPE_FACEBOOK_CONNECTION, + GruschlerFacebookConnectionPrivate); + + self->priv->list_channels = g_hash_table_new_full + (g_direct_hash, g_direct_equal, NULL, g_object_unref); + self->priv->group_channels = g_hash_table_new_full + (g_direct_hash, g_direct_equal, NULL, g_object_unref); +} + +TpBaseConnection * +gruschler_facebook_connection_new (TpBaseConnectionManager *cm, + const GruschlerFacebookConnectionParams *params, + GError **error) +{ + g_return_val_if_fail (TP_IS_BASE_CONNECTION_MANAGER (cm), NULL); + g_return_val_if_fail (NULL != params, NULL); + + if (!params->email || !params->email[0]) + { + g_set_error_literal (error, TP_ERRORS, + TP_ERROR_INVALID_ARGUMENT, + "mandatory email argument is empty"); + return NULL; + } + + if (!params->password || !params->password[0]) + { + g_set_error_literal (error, TP_ERRORS, + TP_ERROR_INVALID_ARGUMENT, + "mandatory password argument is empty"); + return NULL; + } + + return g_object_new (GRUSCHLER_TYPE_FACEBOOK_CONNECTION, + "protocol", "facebook", + "email", params->email, + "password", params->password, NULL); +} + +void * +gruschler_facebook_connection_params_new (void) +{ + return g_new0 (GruschlerFacebookConnectionParams, 1); +} + +void +gruschler_facebook_connection_params_free (void *parsed_params) +{ + GruschlerFacebookConnectionParams *params = parsed_params; + + g_free (params->email); + g_free (params->password); + g_free (params); +} + diff --git a/src/facebook-connection.h b/src/facebook-connection.h new file mode 100644 index 0000000..2e389aa --- /dev/null +++ b/src/facebook-connection.h @@ -0,0 +1,67 @@ +/* telepathy-gruschler - A Telepathy connection manager for social networks. + * Copyright (C) 2009 Mathias Hasselmann + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __GRUSCHLER_FACEBOOK_CONNECTION_H__ +#define __GRUSCHLER_FACEBOOK_CONNECTION_H__ + +#include <telepathy-glib/base-connection-manager.h> + +G_BEGIN_DECLS + +#define GRUSCHLER_TYPE_FACEBOOK_CONNECTION (gruschler_facebook_connection_get_type ()) +#define GRUSCHLER_FACEBOOK_CONNECTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GRUSCHLER_TYPE_FACEBOOK_CONNECTION, GruschlerFacebookConnection)) +#define GRUSCHLER_FACEBOOK_CONNECTION_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST ((cls), GRUSCHLER_TYPE_FACEBOOK_CONNECTION, GruschlerFacebookConnectionClass)) +#define GRUSCHLER_IS_FACEBOOK_CONNECTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GRUSCHLER_TYPE_FACEBOOK_CONNECTION)) +#define GRUSCHLER_IS_FACEBOOK_CONNECTION_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE ((obj), GRUSCHLER_TYPE_FACEBOOK_CONNECTION)) +#define GRUSCHLER_FACEBOOK_CONNECTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GRUSCHLER_TYPE_FACEBOOK_CONNECTION, GruschlerFacebookConnectionClass)) + +typedef struct _GruschlerFacebookConnection GruschlerFacebookConnection; +typedef struct _GruschlerFacebookConnectionClass GruschlerFacebookConnectionClass; +typedef struct _GruschlerFacebookConnectionPrivate GruschlerFacebookConnectionPrivate; +typedef struct _GruschlerFacebookConnectionParams GruschlerFacebookConnectionParams; + +struct _GruschlerFacebookConnection { + TpBaseConnection parent_instance; + GruschlerFacebookConnectionPrivate *priv; +}; + +struct _GruschlerFacebookConnectionClass { + TpBaseConnectionClass parent_class; +}; + +struct _GruschlerFacebookConnectionParams { + char *email; + char *password; +}; + +GType +gruschler_facebook_connection_get_type (void) G_GNUC_CONST; + +TpBaseConnection * +gruschler_facebook_connection_new (TpBaseConnectionManager *cm, + const GruschlerFacebookConnectionParams *params, + GError **error); + +void * +gruschler_facebook_connection_params_new (void); + +void +gruschler_facebook_connection_params_free (void *params); + +G_END_DECLS + +#endif /* __GRUSCHLER_FACEBOOK_CONNECTION_H__ */ diff --git a/src/facebook-contact-list.c b/src/facebook-contact-list.c new file mode 100644 index 0000000..020d8d1 --- /dev/null +++ b/src/facebook-contact-list.c @@ -0,0 +1,508 @@ +/* telepathy-gruschler - A Telepathy connection manager for social networks. + * Copyright (C) 2009 Mathias Hasselmann + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "config.h" +#include "facebook-contact-list.h" + +#include <telepathy-glib/base-connection.h> +#include <telepathy-glib/channel-iface.h> +#include <telepathy-glib/dbus.h> +#include <telepathy-glib/exportable-channel.h> +#include <telepathy-glib/handle.h> +#include <telepathy-glib/interfaces.h> +#include <telepathy-glib/svc-channel.h> +#include <telepathy-glib/svc-generic.h> + +struct _GruschlerFacebookContactListPrivate { + TpBaseConnection *connection; + char *object_path; + + TpHandle handle; + TpHandleType handle_type; + + unsigned closed : 1; + unsigned disposed : 1; +}; + +static void _channel_iface_init (TpSvcChannelClass *iface); + +G_DEFINE_TYPE_WITH_CODE (GruschlerFacebookContactList, + gruschler_facebook_contact_list, + G_TYPE_OBJECT, + + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, _channel_iface_init); + + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP, + tp_group_mixin_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES, + tp_dbus_properties_mixin_iface_init); + + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_CONTACT_LIST, NULL); + G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL); + G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL); +); + +/* property enum */ +enum { + PROP_0, + PROP_OBJECT_PATH, + PROP_CHANNEL_TYPE, + PROP_HANDLE_TYPE, + PROP_HANDLE, + PROP_TARGET_ID, + PROP_REQUESTED, + PROP_INITIATOR_HANDLE, + PROP_INITIATOR_ID, + PROP_CONNECTION, + PROP_INTERFACES, + PROP_CHANNEL_DESTROYED, + PROP_CHANNEL_PROPERTIES, +}; + +const char *_channel_interfaces[] = { + TP_IFACE_CHANNEL_INTERFACE_GROUP, +}; + +static void +gruschler_facebook_contact_list_init (GruschlerFacebookContactList *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + GRUSCHLER_TYPE_FACEBOOK_CONTACT_LIST, + GruschlerFacebookContactListPrivate); +} + +static GObject * +_constructor (GType type, + unsigned n_props, + GObjectConstructParam *props) +{ + GruschlerFacebookContactList *self; + GObject *object; + GObjectClass *parent_class; + TpHandleRepoIface *handle_repo; + TpHandleRepoIface *contact_repo; + + parent_class = G_OBJECT_CLASS (gruschler_facebook_contact_list_parent_class); + object = parent_class->constructor (type, n_props, props); + self = GRUSCHLER_FACEBOOK_CONTACT_LIST (object); + + g_assert (self->priv->handle_type == TP_HANDLE_TYPE_GROUP || + self->priv->handle_type == TP_HANDLE_TYPE_LIST); + + handle_repo = tp_base_connection_get_handles (self->priv->connection, + self->priv->handle_type); + contact_repo = tp_base_connection_get_handles (self->priv->connection, + TP_HANDLE_TYPE_CONTACT); + + g_assert (tp_handle_is_valid (handle_repo, self->priv->handle, NULL)); + tp_handle_ref (handle_repo, self->priv->handle); + + /* initialize group mixin */ + tp_group_mixin_init (object, + G_STRUCT_OFFSET (GruschlerFacebookContactList, group), + contact_repo, self->priv->connection->self_handle); + + if (TP_HANDLE_TYPE_GROUP == self->priv->handle_type) + { + tp_group_mixin_change_flags (object, + TP_CHANNEL_GROUP_FLAG_CAN_ADD | + TP_CHANNEL_GROUP_FLAG_CAN_REMOVE | + TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0); + } + else if (TP_HANDLE_TYPE_LIST != self->priv->handle_type) + { + g_assert_not_reached (); + } + /* magic contact lists from here down... */ + else if (self->priv->handle == GRUSCHLER_FACEBOOK_LIST_HANDLE_PUBLISH) + { + tp_group_mixin_change_flags (object, + TP_CHANNEL_GROUP_FLAG_CAN_REMOVE | + TP_CHANNEL_GROUP_FLAG_MESSAGE_ACCEPT | + TP_CHANNEL_GROUP_FLAG_MESSAGE_REMOVE | + TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0); + } + else if (self->priv->handle == GRUSCHLER_FACEBOOK_LIST_HANDLE_SUBSCRIBE) + { + tp_group_mixin_change_flags (object, + TP_CHANNEL_GROUP_FLAG_CAN_ADD | + TP_CHANNEL_GROUP_FLAG_CAN_REMOVE | + TP_CHANNEL_GROUP_FLAG_CAN_RESCIND | + TP_CHANNEL_GROUP_FLAG_MESSAGE_ADD | + TP_CHANNEL_GROUP_FLAG_MESSAGE_REMOVE | + TP_CHANNEL_GROUP_FLAG_MESSAGE_RESCIND | + TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0); + } + else if (self->priv->handle == GRUSCHLER_FACEBOOK_LIST_HANDLE_STORED) + { + tp_group_mixin_change_flags (object, + TP_CHANNEL_GROUP_FLAG_CAN_REMOVE | + TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0); + } + else if (self->priv->handle == GRUSCHLER_FACEBOOK_LIST_HANDLE_DENY) + { + tp_group_mixin_change_flags (object, + TP_CHANNEL_GROUP_FLAG_CAN_ADD | + TP_CHANNEL_GROUP_FLAG_CAN_REMOVE | + TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0); + } + else + { + g_assert_not_reached (); + } + + /* register object on the bus */ + dbus_g_connection_register_g_object (tp_get_bus(), + self->priv->object_path, object); + + return object; +} + +static void +_get_property (GObject *object, + unsigned prop_id, + GValue *value, + GParamSpec *pspec) +{ + GruschlerFacebookContactList *self; + TpHandleRepoIface *handles; + const char *handle_name; + + self = GRUSCHLER_FACEBOOK_CONTACT_LIST (object); + + switch (prop_id) + { + case PROP_OBJECT_PATH: + g_value_set_string (value, self->priv->object_path); + break; + case PROP_CHANNEL_TYPE: + g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST); + break; + case PROP_HANDLE_TYPE: + g_value_set_uint (value, self->priv->handle_type); + break; + case PROP_HANDLE: + g_value_set_uint (value, self->priv->handle); + break; + case PROP_TARGET_ID: + handles = tp_base_connection_get_handles (self->priv->connection, + self->priv->handle_type); + handle_name = tp_handle_inspect (handles, self->priv->handle); + g_value_set_string (value, handle_name); + break; + case PROP_CONNECTION: + g_value_set_object (value, self->priv->connection); + break; + case PROP_INTERFACES: + g_value_set_boxed (value, _channel_interfaces); + break; + case PROP_INITIATOR_HANDLE: + g_value_set_uint (value, 0); + break; + case PROP_INITIATOR_ID: + g_value_set_static_string (value, ""); + break; + case PROP_REQUESTED: + g_value_set_boolean (value, FALSE); + break; + case PROP_CHANNEL_DESTROYED: + g_value_set_boolean (value, self->priv->closed); + break; + case PROP_CHANNEL_PROPERTIES: + g_value_take_boxed (value, + tp_dbus_properties_mixin_make_properties_hash + (object, + TP_IFACE_CHANNEL, "TargetHandle", + TP_IFACE_CHANNEL, "TargetHandleType", + TP_IFACE_CHANNEL, "ChannelType", + TP_IFACE_CHANNEL, "TargetID", + TP_IFACE_CHANNEL, "InitiatorHandle", + TP_IFACE_CHANNEL, "InitiatorID", + TP_IFACE_CHANNEL, "Requested", + TP_IFACE_CHANNEL, "Interfaces", + NULL)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +_set_property (GObject *object, + unsigned prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GruschlerFacebookContactList *self; + + self = GRUSCHLER_FACEBOOK_CONTACT_LIST (object); + + switch (prop_id) + { + case PROP_OBJECT_PATH: + g_free (self->priv->object_path); + self->priv->object_path = g_value_dup_string (value); + break; + case PROP_CHANNEL_TYPE: + break; /* ignore */ + case PROP_HANDLE_TYPE: + self->priv->handle_type = g_value_get_uint (value); + break; + case PROP_HANDLE: + self->priv->handle = g_value_get_uint (value); + break; + case PROP_INITIATOR_HANDLE: + break; /* ignore */ + case PROP_CONNECTION: + self->priv->connection = g_value_get_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +_dispose (GObject *object) +{ + GruschlerFacebookContactList *self; + + self = GRUSCHLER_FACEBOOK_CONTACT_LIST (object); + + if (!self->priv->disposed) + { + if (!self->priv->closed) + tp_svc_channel_emit_closed (self); + + self->priv->disposed = TRUE; + } + + G_OBJECT_CLASS (gruschler_facebook_contact_list_parent_class)->dispose (object); +} + +static void +_finalize (GObject *object) +{ + GruschlerFacebookContactList *self; + TpHandleRepoIface *handles; + + self = GRUSCHLER_FACEBOOK_CONTACT_LIST (object); + + handles = tp_base_connection_get_handles (self->priv->connection, + self->priv->handle_type); + + tp_handle_unref(handles, self->priv->handle); + + if (self->priv->object_path) + g_free (self->priv->object_path); + + G_OBJECT_CLASS (gruschler_facebook_contact_list_parent_class)->finalize (object); +} + +static gboolean +_group_add_member (GObject *object, + TpHandle handle, + const char *message, + GError **error) +{ + /* FIXME: implement this */ + g_debug ("%s: handle=%u, message=%s", G_STRFUNC, handle, message); + g_return_val_if_reached (FALSE); +} + +static gboolean +_group_remove_member (GObject *object, + TpHandle handle, + const char *message, + GError **error) +{ + /* FIXME: implement this */ + g_debug ("%s: handle=%u, message=%s", G_STRFUNC, handle, message); + g_return_val_if_reached (FALSE); +} + +static void +gruschler_facebook_contact_list_class_init (GruschlerFacebookContactListClass *class) +{ + static TpDBusPropertiesMixinPropImpl channel_props[] = { + { "TargetHandleType", "handle-type", NULL }, + { "TargetHandle", "handle", NULL }, + { "TargetID", "target-id", NULL }, + { "ChannelType", "channel-type", NULL }, + { "Interfaces", "interfaces", NULL }, + { "Requested", "requested", NULL }, + { "InitiatorHandle", "initiator-handle", NULL }, + { "InitiatorID", "initiator-id", NULL }, + { NULL, } + }; + + static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = { + { TP_IFACE_CHANNEL, + tp_dbus_properties_mixin_getter_gobject_properties, + NULL, channel_props, + }, + + { NULL, } + }; + + gsize offset; + GObjectClass *object_class; + GParamSpec *pspec; + + object_class = G_OBJECT_CLASS (class); + + object_class->constructor = _constructor; + object_class->get_property = _get_property; + object_class->set_property = _set_property; + object_class->dispose = _dispose; + object_class->finalize = _finalize; + + g_object_class_override_property (object_class, PROP_OBJECT_PATH, "object-path"); + g_object_class_override_property (object_class, PROP_CHANNEL_TYPE, "channel-type"); + g_object_class_override_property (object_class, PROP_HANDLE_TYPE, "handle-type"); + g_object_class_override_property (object_class, PROP_HANDLE, "handle"); + + g_object_class_override_property (object_class, PROP_CHANNEL_DESTROYED, "channel-destroyed"); + g_object_class_override_property (object_class, PROP_CHANNEL_PROPERTIES, "channel-properties"); + + pspec = g_param_spec_object ("connection", + "Connection", + "The connection that owns this channel.", + TP_TYPE_BASE_CONNECTION, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CONNECTION, pspec); + + pspec = g_param_spec_boxed ("interfaces", + "Interfaces", + "Additional interfaces implemented by channel", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_INTERFACES, pspec); + + pspec = g_param_spec_string ("target-id", + "Target ID", + "Inspected name of this channel's handle", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_TARGET_ID, pspec); + + pspec = g_param_spec_uint ("initiator-handle", + "Initiator handle", + "The contact who initiated the channel", + 0, G_MAXUINT32, 0, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_INITIATOR_HANDLE, pspec); + + pspec = g_param_spec_string ("initiator-id", + "Initiator ID", + "Inspected name of the channel initiator handle", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_INITIATOR_ID, pspec); + + pspec = g_param_spec_boolean ("requested", + "Requested", + "Wheither this channel was requested by the local user", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_REQUESTED, pspec); + + class->properties_class.interfaces = prop_interfaces; + offset = G_STRUCT_OFFSET (GruschlerFacebookContactListClass, properties_class); + tp_dbus_properties_mixin_class_init (object_class, offset); + + offset = G_STRUCT_OFFSET (GruschlerFacebookContactListClass, group_class), + tp_group_mixin_class_init (object_class, offset, + _group_add_member, _group_remove_member); + tp_group_mixin_init_dbus_properties (object_class); + + g_type_class_add_private (class, sizeof (GruschlerFacebookContactListPrivate)); +} + +static void +_channel_close (TpSvcChannel *channel, + DBusGMethodInvocation *context) +{ + GruschlerFacebookContactList *self; + + self = GRUSCHLER_FACEBOOK_CONTACT_LIST (channel); + + if (TP_HANDLE_TYPE_LIST == self->priv->handle_type) + { + GError error = { + TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, + "you may not close contact list channels" + }; + + dbus_g_method_return_error (context, &error); + } + else if (tp_handle_set_size (self->group.members)) + { + GError error = { + TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "you may not close this group, because it's not empty" + }; + + dbus_g_method_return_error (context, &error); + } + else + { + /* TODO: figure out if friend lists can be modified */ + self->priv->closed = TRUE; + tp_svc_channel_emit_closed (self); + tp_svc_channel_return_from_close (context); + } +} + +static void +_channel_get_channel_type (TpSvcChannel *channel, + DBusGMethodInvocation *context) +{ + tp_svc_channel_return_from_get_channel_type (context, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST); +} + + +static void +_channel_get_handle (TpSvcChannel *channel, + DBusGMethodInvocation *context) +{ + tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_CONTACT, + GRUSCHLER_FACEBOOK_CONTACT_LIST (channel)->priv->handle); +} + +static void +_channel_get_interfaces (TpSvcChannel *channel, + DBusGMethodInvocation *context) +{ + tp_svc_channel_return_from_get_interfaces (context, _channel_interfaces); +} + +static void +_channel_iface_init (TpSvcChannelClass *iface) +{ +#define IMPLEMENT(x) \ + tp_svc_channel_implement_##x (iface, _channel_##x) + IMPLEMENT(close); + IMPLEMENT(get_channel_type); + IMPLEMENT(get_handle); + IMPLEMENT(get_interfaces); +#undef IMPLEMENT +} + diff --git a/src/facebook-contact-list.h b/src/facebook-contact-list.h new file mode 100644 index 0000000..a2c8f3c --- /dev/null +++ b/src/facebook-contact-list.h @@ -0,0 +1,62 @@ +/* telepathy-gruschler - A Telepathy connection manager for social networks. + * Copyright (C) 2009 Mathias Hasselmann + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __GRUSCHLER_FACEBOOK_CONTACT_LIST_H__ +#define __GRUSCHLER_FACEBOOK_CONTACT_LIST_H__ + +#include <telepathy-glib/dbus-properties-mixin.h> +#include <telepathy-glib/group-mixin.h> + +G_BEGIN_DECLS + +#define GRUSCHLER_TYPE_FACEBOOK_CONTACT_LIST (gruschler_facebook_contact_list_get_type ()) +#define GRUSCHLER_FACEBOOK_CONTACT_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GRUSCHLER_TYPE_FACEBOOK_CONTACT_LIST, GruschlerFacebookContactList)) +#define GRUSCHLER_FACEBOOK_CONTACT_LIST_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST ((cls), GRUSCHLER_TYPE_FACEBOOK_CONTACT_LIST, GruschlerFacebookContactListClass)) +#define GRUSCHLER_IS_FACEBOOK_CONTACT_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GRUSCHLER_TYPE_FACEBOOK_CONTACT_LIST)) +#define GRUSCHLER_IS_FACEBOOK_CONTACT_LIST_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE ((obj), GRUSCHLER_TYPE_FACEBOOK_CONTACT_LIST)) +#define GRUSCHLER_FACEBOOK_CONTACT_LIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GRUSCHLER_TYPE_FACEBOOK_CONTACT_LIST, GruschlerFacebookContactListClass)) + +typedef enum { + GRUSCHLER_FACEBOOK_LIST_HANDLE_PUBLISH = 1, + GRUSCHLER_FACEBOOK_LIST_HANDLE_SUBSCRIBE, + GRUSCHLER_FACEBOOK_LIST_HANDLE_STORED, + GRUSCHLER_FACEBOOK_LIST_HANDLE_DENY, +} GruschlerFacebookListHandle; + +typedef struct _GruschlerFacebookContactList GruschlerFacebookContactList; +typedef struct _GruschlerFacebookContactListClass GruschlerFacebookContactListClass; +typedef struct _GruschlerFacebookContactListPrivate GruschlerFacebookContactListPrivate; + +struct _GruschlerFacebookContactList { + GObject parent_instance; + TpGroupMixin group; + GruschlerFacebookContactListPrivate *priv; +}; + +struct _GruschlerFacebookContactListClass { + GObjectClass parent_class; + TpGroupMixinClass group_class; + TpDBusPropertiesMixinClass properties_class; +}; + +GType +gruschler_facebook_contact_list_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* __GRUSCHLER_FACEBOOK_CONTACT_LIST_H__ */ + diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..1feaf2e --- /dev/null +++ b/src/main.c @@ -0,0 +1,35 @@ +/* telepathy-gruschler - A Telepathy connection manager for social networks. + * Copyright (C) 2009 Mathias Hasselmann + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "config.h" +#include "connection-manager.h" + +#include <telepathy-glib/debug.h> +#include <telepathy-glib/run.h> + +int +main (int argc, + char **argv) +{ + g_thread_init (NULL); + tp_debug_set_persistent (TRUE); + + return tp_run_connection_manager (PACKAGE, VERSION, + gruschler_connection_manager_new, + argc, argv); +} + |