diff options
-rw-r--r-- | extensions/Logger.xml | 83 | ||||
-rw-r--r-- | telepathy-logger/Makefile.am | 2 | ||||
-rw-r--r-- | telepathy-logger/action-chain.c | 42 | ||||
-rw-r--r-- | telepathy-logger/action-chain.h | 8 | ||||
-rw-r--r-- | telepathy-logger/channel-text.c | 56 | ||||
-rw-r--r-- | telepathy-logger/channel.c | 24 | ||||
-rw-r--r-- | telepathy-logger/dbus-service.c | 856 | ||||
-rw-r--r-- | telepathy-logger/dbus-service.h | 6 | ||||
-rw-r--r-- | tests/tpl-channel-test.c | 8 |
9 files changed, 970 insertions, 115 deletions
diff --git a/extensions/Logger.xml b/extensions/Logger.xml index 1695690..a377829 100644 --- a/extensions/Logger.xml +++ b/extensions/Logger.xml @@ -91,6 +91,89 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</ </tp:docstring> </method> + <method name="GetFavouriteContacts" + tp:name-for-bindings="Get_Favourite_Contacts"> + <arg direction="out" name="Favourite_Contacts" type="a(oas)"> + <tp:docstring> + The favourite contacts, as an array of TpAccounts and their contact + identifiers. + </tp:docstring> + </arg> + + <tp:docstring> + Returns the favourite contacts. + </tp:docstring> + </method> + + <method name="AddFavouriteContact" + tp:name-for-bindings="Add_Favourite_Contact"> + <arg direction="in" name="Account" type="o" tp:type="Account"> + <tp:docstring> + The object path for the TpAccount to which the contact belongs + </tp:docstring> + </arg> + + <arg direction="in" name="Identifier" type="s"> + <tp:docstring> + The favourite contact's identifier + </tp:docstring> + </arg> + + <tp:docstring> + Add a contact's designation as a favourite. This method may not be + called until the service is ready. See the <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Logger.DRAFT">FavouriteContactsReady</tp:dbus-ref> signal and <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Logger.DRAFT">FavouriteContactsIsReady</tp:dbus-ref> property. + </tp:docstring> + </method> + + <method name="RemoveFavouriteContact" + tp:name-for-bindings="Remove_Favourite_Contact"> + <arg direction="in" name="Account" type="o" tp:type="Account"> + <tp:docstring> + The object path for the TpAccount to which the contact belongs + </tp:docstring> + </arg> + + <arg direction="in" name="Identifier" type="s"> + <tp:docstring> + The favourite contact's identifier + </tp:docstring> + </arg> + + <tp:docstring> + Remove a contact's designation as a favourite. This method may not be + called until the service is ready. See the <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Logger.DRAFT">FavouriteContactsReady</tp:dbus-ref> signal and <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Logger.DRAFT">FavouriteContactsIsReady</tp:dbus-ref> property. + </tp:docstring> + </method> + + <signal name="FavouriteContactsChanged" + tp:name-for-bindings="Favourite_Contacts_Changed"> + <tp:docstring> + The set of favourite contacts has changed. + </tp:docstring> + + <arg name="Account" type="o" tp:type="Account"> + <tp:docstring> + An account associated with the contact. + </tp:docstring> + </arg> + + <arg name="Added" type="as"> + <tp:docstring> + List of contact identifiers of contacts which are now favourites. + </tp:docstring> + </arg> + + <arg name="Removed" type="as"> + <tp:docstring> + List of contact identifiers of contacts which are no longer favourites. + </tp:docstring> + </arg> + </signal> + </interface> </node> <!-- vim:set sw=2 sts=2 et ft=xml: --> diff --git a/telepathy-logger/Makefile.am b/telepathy-logger/Makefile.am index 3f2021c..3892482 100644 --- a/telepathy-logger/Makefile.am +++ b/telepathy-logger/Makefile.am @@ -6,6 +6,7 @@ AM_CPPFLAGS = \ -I$(top_srcdir) \ $(ERROR_CFLAGS) \ -DG_LOG_DOMAIN=\"tp-logger\" \ + -DTPL_DATA_DIR=\"$(PACKAGE_NAME)\" \ $(LIBTPL_CFLAGS) \ $(DISABLE_DEPRECATED) \ $(WARN_CFLAGS) @@ -38,7 +39,6 @@ LIBTPL_HEADERS = \ observer.h \ util.h - libtelepathy_logger_la_SOURCES = \ action-chain.c \ channel.c \ diff --git a/telepathy-logger/action-chain.c b/telepathy-logger/action-chain.c index 7d4af57..f29a9c5 100644 --- a/telepathy-logger/action-chain.c +++ b/telepathy-logger/action-chain.c @@ -22,6 +22,12 @@ #include "config.h" #include "action-chain.h" +typedef struct { + TplPendingAction action; + gpointer user_data; +} TplActionLink; + + TplActionChain * tpl_actionchain_new (GObject *obj, GAsyncReadyCallback cb, @@ -37,9 +43,17 @@ tpl_actionchain_new (GObject *obj, } +static void +link_free (TplActionLink *link) +{ + g_slice_free (TplActionLink, link); +} + + void tpl_actionchain_free (TplActionChain *self) { + g_queue_foreach (self->chain, (GFunc) link_free, NULL); g_queue_free (self->chain); /* TODO free self->simple, I canont understand how */ g_slice_free (TplActionChain, self); @@ -57,17 +71,31 @@ tpl_actionchain_get_object (TplActionChain *self) void tpl_actionchain_prepend (TplActionChain *self, - TplPendingAction func) + TplPendingAction func, + gpointer user_data) { - g_queue_push_head (self->chain, func); + TplActionLink *link; + + link = g_slice_new0 (TplActionLink); + link->action = func; + link->user_data = user_data; + + g_queue_push_head (self->chain, link); } void tpl_actionchain_append (TplActionChain *self, - TplPendingAction func) + TplPendingAction func, + gpointer user_data) { - g_queue_push_tail (self->chain, func); + TplActionLink *link; + + link = g_slice_new0 (TplActionLink); + link->action = func; + link->user_data = user_data; + + g_queue_push_tail (self->chain, link); } @@ -83,8 +111,10 @@ tpl_actionchain_continue (TplActionChain *self) } else { - TplPendingAction next_action = g_queue_pop_head (self->chain); - next_action (self); + TplActionLink *link = g_queue_pop_head (self->chain); + + link->action (self, link->user_data); + link_free (link); } } diff --git a/telepathy-logger/action-chain.h b/telepathy-logger/action-chain.h index f5027e1..75621fc 100644 --- a/telepathy-logger/action-chain.h +++ b/telepathy-logger/action-chain.h @@ -33,9 +33,11 @@ typedef struct { TplActionChain *tpl_actionchain_new (GObject *obj, GAsyncReadyCallback cb, gpointer user_data); void tpl_actionchain_free (TplActionChain *self); -typedef void (*TplPendingAction) (TplActionChain *ctx); -void tpl_actionchain_append (TplActionChain *self, TplPendingAction func); -void tpl_actionchain_prepend (TplActionChain *self, TplPendingAction func); +typedef void (*TplPendingAction) (TplActionChain *ctx, gpointer user_data); +void tpl_actionchain_append (TplActionChain *self, TplPendingAction func, + gpointer user_data); +void tpl_actionchain_prepend (TplActionChain *self, TplPendingAction func, + gpointer user_data); void tpl_actionchain_continue (TplActionChain *self); void tpl_actionchain_terminate (TplActionChain *self); gpointer tpl_actionchain_get_object (TplActionChain *self); diff --git a/telepathy-logger/channel-text.c b/telepathy-logger/channel-text.c index ee62527..159e237 100644 --- a/telepathy-logger/channel-text.c +++ b/telepathy-logger/channel-text.c @@ -78,16 +78,23 @@ static void on_sent_signal_cb (TpChannel *proxy, guint arg_Timestamp, static void on_send_error_cb (TpChannel *proxy, guint arg_Error, guint arg_Timestamp, guint arg_Type, const gchar *arg_Text, gpointer user_data, GObject *weak_object); -static void pendingproc_connect_signals (TplActionChain *ctx); -static void pendingproc_get_pending_messages (TplActionChain *ctx); -static void pendingproc_prepare_tpl_channel (TplActionChain *ctx); -static void pendingproc_get_chatroom_id (TplActionChain *ctx); +static void pendingproc_connect_signals (TplActionChain *ctx, + gpointer user_data); +static void pendingproc_get_pending_messages (TplActionChain *ctx, + gpointer user_data); +static void pendingproc_prepare_tpl_channel (TplActionChain *ctx, + gpointer user_data); +static void pendingproc_get_chatroom_id (TplActionChain *ctx, + gpointer user_data); static void get_chatroom_id_cb (TpConnection *proxy, const gchar **identifiers, const GError *error, gpointer user_data, GObject *weak_object); -static void pendingproc_get_my_contact (TplActionChain *ctx); -static void pendingproc_get_remote_contact (TplActionChain *ctx); -static void pendingproc_get_remote_handle_type (TplActionChain *ctx); +static void pendingproc_get_my_contact (TplActionChain *ctx, + gpointer user_data); +static void pendingproc_get_remote_contact (TplActionChain *ctx, + gpointer user_data); +static void pendingproc_get_remote_handle_type (TplActionChain *ctx, + gpointer user_data); static void keepon_on_receiving_signal (TplLogEntryText *log); @@ -157,7 +164,8 @@ got_contact_cb (TpConnection *connection, static void -pendingproc_get_remote_contact (TplActionChain *ctx) +pendingproc_get_remote_contact (TplActionChain *ctx, + gpointer user_data) { TplChannelText *tpl_text = tpl_actionchain_get_object (ctx); TplChannel *tpl_chan = TPL_CHANNEL (tpl_text); @@ -174,7 +182,8 @@ pendingproc_get_remote_contact (TplActionChain *ctx) static void -pendingproc_get_my_contact (TplActionChain *ctx) +pendingproc_get_my_contact (TplActionChain *ctx, + gpointer user_data) { TplChannelText *tpl_text = tpl_actionchain_get_object (ctx); TpConnection *tp_conn = tp_channel_borrow_connection ( @@ -188,7 +197,8 @@ pendingproc_get_my_contact (TplActionChain *ctx) static void -pendingproc_get_remote_handle_type (TplActionChain *ctx) +pendingproc_get_remote_handle_type (TplActionChain *ctx, + gpointer user_data) { TplChannelText *tpl_text = tpl_actionchain_get_object (ctx); TpHandleType remote_handle_type; @@ -198,10 +208,10 @@ pendingproc_get_remote_handle_type (TplActionChain *ctx) switch (remote_handle_type) { case TP_HANDLE_TYPE_CONTACT: - tpl_actionchain_prepend (ctx, pendingproc_get_remote_contact); + tpl_actionchain_prepend (ctx, pendingproc_get_remote_contact, NULL); break; case TP_HANDLE_TYPE_ROOM: - tpl_actionchain_prepend (ctx, pendingproc_get_chatroom_id); + tpl_actionchain_prepend (ctx, pendingproc_get_chatroom_id, NULL); break; case TP_HANDLE_TYPE_NONE: PATH_DEBUG (tpl_text, "HANDLE_TYPE_NONE received, probably an anonymous " @@ -472,18 +482,19 @@ tpl_channel_text_call_when_ready (TplChannelText *self, * are unreferenced by g_object_unref but used by a next action AND what object are actually not * prepared but used anyway */ actions = tpl_actionchain_new (G_OBJECT (self), cb, user_data); - tpl_actionchain_append (actions, pendingproc_connect_signals); - tpl_actionchain_append (actions, pendingproc_prepare_tpl_channel); - tpl_actionchain_append (actions, pendingproc_get_my_contact); - tpl_actionchain_append (actions, pendingproc_get_remote_handle_type); - tpl_actionchain_append (actions, pendingproc_get_pending_messages); + tpl_actionchain_append (actions, pendingproc_connect_signals, NULL); + tpl_actionchain_append (actions, pendingproc_prepare_tpl_channel, NULL); + tpl_actionchain_append (actions, pendingproc_get_my_contact, NULL); + tpl_actionchain_append (actions, pendingproc_get_remote_handle_type, NULL); + tpl_actionchain_append (actions, pendingproc_get_pending_messages, NULL); /* start the chain consuming */ tpl_actionchain_continue (actions); } static void -pendingproc_prepare_tpl_channel (TplActionChain *ctx) +pendingproc_prepare_tpl_channel (TplActionChain *ctx, + gpointer user_data) { TplChannel *tpl_chan = TPL_CHANNEL (tpl_actionchain_get_object (ctx)); @@ -509,7 +520,8 @@ got_tpl_chan_ready_cb (GObject *obj, static void -pendingproc_get_pending_messages (TplActionChain *ctx) +pendingproc_get_pending_messages (TplActionChain *ctx, + gpointer user_data) { TplChannelText *chan_text = tpl_actionchain_get_object (ctx); @@ -567,7 +579,8 @@ got_pending_messages_cb (TpChannel *proxy, static void -pendingproc_get_chatroom_id (TplActionChain *ctx) +pendingproc_get_chatroom_id (TplActionChain *ctx, + gpointer user_data) { TplChannelText *tpl_text = tpl_actionchain_get_object (ctx); TplChannel *tpl_chan = TPL_CHANNEL (tpl_text); @@ -612,7 +625,8 @@ get_chatroom_id_cb (TpConnection *proxy, static void -pendingproc_connect_signals (TplActionChain *ctx) +pendingproc_connect_signals (TplActionChain *ctx, + gpointer user_data) { TplChannelText *tpl_text = tpl_actionchain_get_object (ctx); GError *error = NULL; diff --git a/telepathy-logger/channel.c b/telepathy-logger/channel.c index 7772f4c..814d73e 100644 --- a/telepathy-logger/channel.c +++ b/telepathy-logger/channel.c @@ -41,13 +41,16 @@ static void tpl_channel_set_account (TplChannel *self, TpAccount *data); static void call_when_ready_protected (TplChannel *self, GAsyncReadyCallback cb, gpointer user_data); -static void pendingproc_get_ready_tp_connection (TplActionChain *ctx); +static void pendingproc_get_ready_tp_connection (TplActionChain *ctx, + gpointer user_data); static void got_ready_tp_connection_cb (TpConnection *connection, const GError *error, gpointer user_data); -static void pendingproc_get_ready_tp_channel (TplActionChain *ctx); +static void pendingproc_get_ready_tp_channel (TplActionChain *ctx, + gpointer user_data); static void got_ready_tp_channel_cb (TpChannel *channel, const GError *error, gpointer user_data); -static void pendingproc_register_tpl_channel (TplActionChain *ctx); +static void pendingproc_register_tpl_channel (TplActionChain *ctx, + gpointer user_data); G_DEFINE_ABSTRACT_TYPE (TplChannel, tpl_channel, TP_TYPE_CHANNEL) @@ -229,15 +232,16 @@ call_when_ready_protected (TplChannel *self, TplActionChain *actions; actions = tpl_actionchain_new (G_OBJECT (self), cb, user_data); - tpl_actionchain_append (actions, pendingproc_get_ready_tp_connection); - tpl_actionchain_append (actions, pendingproc_get_ready_tp_channel); - tpl_actionchain_append (actions, pendingproc_register_tpl_channel); + tpl_actionchain_append (actions, pendingproc_get_ready_tp_connection, NULL); + tpl_actionchain_append (actions, pendingproc_get_ready_tp_channel, NULL); + tpl_actionchain_append (actions, pendingproc_register_tpl_channel, NULL); tpl_actionchain_continue (actions); } static void -pendingproc_get_ready_tp_connection (TplActionChain *ctx) +pendingproc_get_ready_tp_connection (TplActionChain *ctx, + gpointer user_data) { TplChannel *tpl_chan = tpl_actionchain_get_object (ctx); TpConnection *tp_conn = tp_channel_borrow_connection (TP_CHANNEL ( @@ -272,7 +276,8 @@ got_ready_tp_connection_cb (TpConnection *connection, } static void -pendingproc_get_ready_tp_channel (TplActionChain *ctx) +pendingproc_get_ready_tp_channel (TplActionChain *ctx, + gpointer user_data) { TplChannel *tpl_chan = tpl_actionchain_get_object (ctx); @@ -311,7 +316,8 @@ got_ready_tp_channel_cb (TpChannel *channel, static void -pendingproc_register_tpl_channel (TplActionChain *ctx) +pendingproc_register_tpl_channel (TplActionChain *ctx, + gpointer user_data) { /* singleton */ TplObserver *observer = tpl_observer_new (); diff --git a/telepathy-logger/dbus-service.c b/telepathy-logger/dbus-service.c index 6f00c44..fa000b6 100644 --- a/telepathy-logger/dbus-service.c +++ b/telepathy-logger/dbus-service.c @@ -22,11 +22,16 @@ #include "config.h" #include "dbus-service.h" +#include <string.h> +#include <sys/stat.h> + #include <glib.h> #include <telepathy-glib/dbus.h> #include <telepathy-glib/account.h> #include <telepathy-glib/util.h> +#include <telepathy-glib/svc-generic.h> +#include <telepathy-logger/action-chain.h> #include <telepathy-logger/log-entry-text.h> #include <telepathy-logger/log-manager.h> #include <telepathy-logger/util.h> @@ -36,22 +41,306 @@ #define DEBUG_FLAG TPL_DEBUG_DBUS_SERVICE #include <telepathy-logger/debug.h> +#define FAVOURITE_CONTACTS_FILENAME "favourite-contacts.txt" + static void tpl_logger_iface_init (gpointer iface, gpointer iface_data); #define GET_PRIV(obj) TPL_GET_PRIV (obj, TplDBusService) struct _TplDBusServicePriv { TplLogManager *manager; + /* map of (string) account name -> (string set) contact ID */ + /* (the set is implemented as a hash table) */ + GHashTable *accounts_contacts_map; + TplActionChain *favourite_contacts_actions; }; G_DEFINE_TYPE_WITH_CODE (TplDBusService, tpl_dbus_service, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (TPL_TYPE_SVC_LOGGER, tpl_logger_iface_init)); +typedef struct _FavouriteContactClosure FavouriteContactClosure; +typedef void (*FavouriteContactCallback) (gboolean success, + FavouriteContactClosure *closure); + + +struct _FavouriteContactClosure { + TplDBusService *service; + gchar *account; + gchar *contact_id; + gchar *file_contents; + DBusGMethodInvocation *context; + FavouriteContactCallback cb; +}; + + +static void +favourite_contact_closure_free (FavouriteContactClosure *closure) +{ + if (closure == NULL) + return; + + if (closure->service != NULL) + g_object_unref (closure->service); + + g_free (closure->account); + g_free (closure->contact_id); + g_free (closure->file_contents); + g_slice_free (FavouriteContactClosure, closure); +} + + +static FavouriteContactClosure * +favourite_contact_closure_new (TplDBusService *self, + const gchar *account, + const gchar *contact_id, + DBusGMethodInvocation *context) +{ + FavouriteContactClosure *closure; + + closure = g_slice_new0 (FavouriteContactClosure); + closure->service = g_object_ref (G_OBJECT (self)); + closure->account = g_strdup (account); + closure->contact_id = g_strdup (contact_id); + /* XXX: ideally we'd up the ref count or duplicate this */ + closure->context = context; + + return closure; +} + + +static gboolean +favourite_contacts_add_entry (TplDBusService *self, + const gchar *account, + const gchar *contact_id) +{ + GHashTable *contacts; + gboolean new_entry = FALSE; + TplDBusServicePriv *priv; + + g_return_val_if_fail (TPL_IS_DBUS_SERVICE (self), FALSE); + g_return_val_if_fail (account != NULL, FALSE); + g_return_val_if_fail (contact_id != NULL, FALSE); + + priv = GET_PRIV (self); + + DEBUG ("adding favourite contact: account '%s', ID '%s'", + account, contact_id); + + contacts = g_hash_table_lookup (priv->accounts_contacts_map, account); + if (contacts == NULL) + { + contacts = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, NULL); + g_hash_table_insert (priv->accounts_contacts_map, g_strdup (account), + contacts); + new_entry = TRUE; + } + else if (g_hash_table_lookup (contacts, contact_id) == NULL) + { + new_entry = TRUE; + } + + if (new_entry) + { + /* add dummy string for the value just for the convenience of looking up + * whether the key already exists */ + g_hash_table_insert (contacts, g_strdup (contact_id), + GINT_TO_POINTER (TRUE)); + } + + return new_entry; +} + + +static const gchar * +favourite_contacts_get_filename (void) +{ + static gchar *filename = NULL; + + if (filename == NULL) + { + filename = g_build_filename (g_get_user_data_dir (), TPL_DATA_DIR, + FAVOURITE_CONTACTS_FILENAME, NULL); + } + + return filename; +} + + +static gboolean +favourite_contacts_parse_line (TplDBusService *self, + const gchar *line) +{ + gboolean success = TRUE; + gchar **elements; + + if (line == NULL || line[0] == '\0') + return TRUE; + + /* this works on the assumption that account names can't have spaces in them + */ + elements = g_strsplit (line, " ", 2); + if (g_strv_length (elements) < 2) + { + DEBUG ("invalid number of elements on favourite contacts file line:\n" + "%s\n", line); + success = FALSE; + } + else + favourite_contacts_add_entry (self, elements[0], elements[1]); + + g_strfreev (elements); + + return success; +} + + +static void +favourite_contacts_file_read_line_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GDataInputStream *data_stream = G_DATA_INPUT_STREAM (object); + TplActionChain *action_chain = (TplActionChain *) (user_data); + TplDBusService *self = tpl_actionchain_get_object (action_chain); + TplDBusServicePriv *priv; + gchar *line; + GError *error = NULL; + + priv = GET_PRIV (self); + + line = g_data_input_stream_read_line_finish (data_stream, result, NULL, &error); + + if (error != NULL) + { + DEBUG ("failed to open favourite contacts file: %s", error->message); + g_clear_error (&error); + tpl_actionchain_terminate (action_chain); + } + else if (line != NULL) + { + favourite_contacts_parse_line (self, line); + + g_data_input_stream_read_line_async (data_stream, G_PRIORITY_DEFAULT, + NULL, favourite_contacts_file_read_line_cb, action_chain); + } + else + tpl_actionchain_continue (action_chain); +} + + +static void +favourite_contacts_file_open_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GFile *file = G_FILE (object); + TplActionChain *action_chain = (TplActionChain *) user_data; + GFileInputStream *stream; + GError *error = NULL; + + if ((stream = g_file_read_finish (file, result, &error))) + { + GDataInputStream *data_stream = g_data_input_stream_new ( + G_INPUT_STREAM (stream)); + + g_data_input_stream_read_line_async (data_stream, G_PRIORITY_DEFAULT, + NULL, favourite_contacts_file_read_line_cb, action_chain); + + g_object_unref (stream); + } + else if (error->code == G_IO_ERROR_NOT_FOUND) + { + DEBUG ("Favourite contacts file doesn't exist yet. Will create as " + "necessary."); + + g_clear_error (&error); + tpl_actionchain_continue (action_chain); + } + else + { + DEBUG ("Failed to open the favourite contacts file: %s", error->message); + g_clear_error (&error); + tpl_actionchain_terminate (action_chain); + } +} + + +static void +pendingproc_favourite_contacts_file_open (TplActionChain *action_chain, + gpointer user_data) +{ + const gchar *filename; + GFile *file; + + filename = favourite_contacts_get_filename (); + file = g_file_new_for_path (filename); + + g_file_read_async (file, G_PRIORITY_DEFAULT, NULL, + favourite_contacts_file_open_cb, action_chain); + + g_object_unref (G_OBJECT (file)); +} + + +static void +tpl_dbus_service_dispose (GObject *obj) +{ + TplDBusServicePriv *priv = GET_PRIV (obj); + + if (priv->accounts_contacts_map != NULL) + { + g_hash_table_destroy (priv->accounts_contacts_map); + priv->accounts_contacts_map = NULL; + } + + if (priv->favourite_contacts_actions != NULL) + priv->favourite_contacts_actions = NULL; + + G_OBJECT_CLASS (tpl_dbus_service_parent_class)->dispose (obj); +} + + +static void +favourite_contacts_file_parsed_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + TplDBusService *self = TPL_DBUS_SERVICE (object); + TplDBusServicePriv *priv = GET_PRIV (self); + + if (!tpl_actionchain_finish (result)) + { + DEBUG ("Failed to parse the favourite contacts file and/or execute " + "subsequent queued method calls"); + } + + priv->favourite_contacts_actions = NULL; +} + + +static void +tpl_dbus_service_constructed (GObject *object) +{ + TplDBusServicePriv *priv = GET_PRIV (object); + + priv->favourite_contacts_actions = tpl_actionchain_new (object, + favourite_contacts_file_parsed_cb, object); + + tpl_actionchain_append (priv->favourite_contacts_actions, + pendingproc_favourite_contacts_file_open, NULL); + tpl_actionchain_continue (priv->favourite_contacts_actions); +} + + static void tpl_dbus_service_class_init (TplDBusServiceClass *klass) { GObjectClass* object_class = G_OBJECT_CLASS (klass); + object_class->constructed = tpl_dbus_service_constructed; + object_class->dispose = tpl_dbus_service_dispose; + g_type_class_add_private (object_class, sizeof (TplDBusServicePriv)); } @@ -66,6 +355,9 @@ tpl_dbus_service_init (TplDBusService *self) self->priv = priv; priv->manager = tpl_log_manager_dup_singleton (); + priv->accounts_contacts_map = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, (GDestroyNotify) g_hash_table_destroy); + priv->favourite_contacts_actions = NULL; } @@ -76,39 +368,134 @@ tpl_dbus_service_new (void) } -static GPtrArray * -tpl_chat_message_marshal (GList *data) +typedef struct +{ + TplDBusService *self; + TpAccount *account; + char *identifier; + gboolean is_chatroom; + guint lines; + DBusGMethodInvocation *context; + GPtrArray *packed; + GList *dates, *ptr; +} RecentMessagesContext; + +static void _lookup_next_date (RecentMessagesContext *ctx); + +static void +_get_messages_return (GObject *manager, + GAsyncResult *res, + gpointer user_data) { - guint idx; - GList *data_ptr; - GPtrArray *retval; + RecentMessagesContext *ctx = user_data; + GList *messages, *ptr; + GError *error = NULL; - retval = g_ptr_array_new_with_free_func ((GDestroyNotify) g_value_array_free); + messages = tpl_log_manager_get_messages_for_date_async_finish (res, &error); + if (error != NULL) + { + DEBUG ("Failed to get messages: %s", error->message); - DEBUG ("Marshalled a(ssx) data:"); + g_clear_error (&error); + messages = NULL; /* just to be sure */ + } - for (idx = 0, data_ptr = data; - data_ptr != NULL; - data_ptr = g_list_next (data_ptr), ++idx) + /* from the most recent message, backward */ + for (ptr = g_list_last (messages); + ptr != NULL && ctx->lines > 0; + ptr = g_list_previous (ptr)) { - TplLogEntry *log = data_ptr->data; - const gchar *message = tpl_log_entry_text_get_message ( + TplLogEntry *log = ptr->data; + const char *message = tpl_log_entry_text_get_message ( TPL_LOG_ENTRY_TEXT (log)); - const gchar *sender = tpl_contact_get_identifier ( + const char *sender = tpl_contact_get_identifier ( tpl_log_entry_text_get_sender (TPL_LOG_ENTRY_TEXT (log))); gint64 timestamp = tpl_log_entry_get_timestamp (log); - g_ptr_array_add (retval, tp_value_array_build (3, + DEBUG ("Message: %" G_GINT64_FORMAT " <%s> %s", + timestamp, sender, message); + + g_ptr_array_add (ctx->packed, tp_value_array_build (3, G_TYPE_STRING, sender, G_TYPE_STRING, message, G_TYPE_INT64, timestamp, G_TYPE_INVALID)); - DEBUG ("%d = %s / %s / %" G_GINT64_FORMAT, idx, sender, message, - timestamp); + ctx->lines--; } - return retval; + g_list_foreach (messages, (GFunc) g_object_unref, NULL); + g_list_free (messages); + + _lookup_next_date (ctx); +} + + +static void +_lookup_next_date (RecentMessagesContext *ctx) +{ + TplDBusServicePriv *priv = GET_PRIV (ctx->self); + + if (ctx->ptr != NULL && ctx->lines > 0) + { + char *date = ctx->ptr->data; + + DEBUG ("Looking up date %s", date); + + tpl_log_manager_get_messages_for_date_async (priv->manager, + ctx->account, ctx->identifier, ctx->is_chatroom, date, + _get_messages_return, ctx); + + ctx->ptr = g_list_previous (ctx->ptr); + } + else + { + /* return and release */ + DEBUG ("complete, returning"); + + g_list_foreach (ctx->dates, (GFunc) g_free, NULL); + g_list_free (ctx->dates); + + tpl_svc_logger_return_from_get_recent_messages (ctx->context, + ctx->packed); + + g_ptr_array_free (ctx->packed, TRUE); + g_free (ctx->identifier); + g_object_unref (ctx->account); + g_slice_free (RecentMessagesContext, ctx); + } +} + + +static void +_get_dates_return (GObject *manager, + GAsyncResult *res, + gpointer user_data) +{ + RecentMessagesContext *ctx = user_data; + GError *error = NULL; + + ctx->dates = tpl_log_manager_get_dates_async_finish (res, &error); + if (ctx->dates == NULL) + { + DEBUG ("Failed to get dates: %s", error->message); + + dbus_g_method_return_error (ctx->context, error); + + g_clear_error (&error); + + g_free (ctx->identifier); + g_object_unref (ctx->account); + g_slice_free (RecentMessagesContext, ctx); + + return; + } + + ctx->ptr = g_list_last (ctx->dates); + ctx->packed = g_ptr_array_new_with_free_func ( + (GDestroyNotify) g_value_array_free); + + _lookup_next_date (ctx); } @@ -121,14 +508,10 @@ tpl_dbus_service_get_recent_messages (TplSvcLogger *self, DBusGMethodInvocation *context) { TplDBusServicePriv *priv = GET_PRIV (self); - TpAccount *account = NULL; - TpDBusDaemon *tp_dbus = NULL; - GList *ret = NULL; - GPtrArray *packed = NULL; - GList *dates = NULL; - GList *dates_ptr = NULL; + TpDBusDaemon *tp_dbus; + TpAccount *account; + RecentMessagesContext *ctx; GError *error = NULL; - guint left_lines = lines; g_return_if_fail (TPL_IS_DBUS_SERVICE (self)); g_return_if_fail (context != NULL); @@ -150,56 +533,19 @@ tpl_dbus_service_get_recent_messages (TplSvcLogger *self, goto out; } - dates = tpl_log_manager_get_dates (priv->manager, account, identifier, - is_chatroom); - if (dates == NULL) - { - error = g_error_new_literal (TPL_DBUS_SERVICE_ERROR, - TPL_DBUS_SERVICE_ERROR_FAILED, "Error during date list retrieving, " - "probably the account path or the identifier does not exist"); - dbus_g_method_return_error (context, error); - goto out; - } + ctx = g_slice_new (RecentMessagesContext); + ctx->self = TPL_DBUS_SERVICE (self); + ctx->account = account; + ctx->identifier = g_strdup (identifier); + ctx->is_chatroom = is_chatroom; + ctx->lines = lines; + ctx->context = context; - /* for each date returned, get at most <lines> lines, then if needed - * check the previous date for the missing ones, and so on until - * <lines> is reached, most recent date first */ - for (dates_ptr = g_list_last (dates); - dates_ptr != NULL && left_lines > 0; - dates_ptr = g_list_previous (dates_ptr)) - { - gchar *date = dates_ptr->data; - GList *messages = tpl_log_manager_get_messages_for_date (priv->manager, - account, identifier, is_chatroom, date); - GList *messages_ptr; - - /* from the most recent message, backward */ - for (messages_ptr = g_list_last (messages); - messages_ptr != NULL && left_lines > 0; - messages_ptr = g_list_previous (messages_ptr)) - { - TplLogEntry *log = messages_ptr->data; - /* keeps the reference and add to the result */ - ret = g_list_prepend (ret, g_object_ref (log)); - left_lines -= 1; - } - g_list_foreach (messages, (GFunc) g_object_unref, NULL); - g_list_free (messages); - } - g_list_foreach (dates, (GFunc) g_free, NULL); - g_list_free (dates); - - packed = tpl_chat_message_marshal (ret); - - tpl_svc_logger_return_from_get_recent_messages (context, packed); - - g_list_foreach (ret, (GFunc) g_object_unref, NULL); - g_list_free (ret); - g_ptr_array_free (packed, TRUE); + tpl_log_manager_get_dates_async (priv->manager, + account, identifier, is_chatroom, + _get_dates_return, ctx); out: - if (account != NULL) - g_object_unref (account); if (tp_dbus != NULL) g_object_unref (tp_dbus); @@ -209,6 +555,371 @@ out: static void +append_favourite_contacts_account_and_contacts (const gchar *account, + GHashTable *contacts, + GPtrArray *packed) +{ + GList *l; + gchar **contact_ids; + gint i; + + /* this case shouldn't happen, but this is just some basic sanity checking */ + if (g_hash_table_size (contacts) < 1) + return; + + /* includes room for the terminal NULL */ + contact_ids = g_new0 (gchar *, g_hash_table_size (contacts)+1); + + for (i = 0, l = g_hash_table_get_keys (contacts); + l; + i++, l = g_list_delete_link (l, l)) + { + contact_ids[i] = l->data; + } + + g_ptr_array_add (packed, tp_value_array_build (2, + DBUS_TYPE_G_OBJECT_PATH, account, + G_TYPE_STRV, contact_ids, + G_TYPE_INVALID)); + + g_free (contact_ids); +} + + +static void +pendingproc_get_favourite_contacts (TplActionChain *action_chain, + gpointer user_data) +{ + FavouriteContactClosure *closure = user_data; + TplDBusServicePriv *priv; + GPtrArray *packed; + + g_return_if_fail (closure); + g_return_if_fail (TPL_IS_DBUS_SERVICE (closure->service)); + g_return_if_fail (closure->context != NULL); + + priv = GET_PRIV (closure->service); + + packed = g_ptr_array_new_with_free_func ((GDestroyNotify) g_value_array_free); + + g_hash_table_foreach (priv->accounts_contacts_map, + (GHFunc) append_favourite_contacts_account_and_contacts, packed); + + tpl_svc_logger_return_from_get_favourite_contacts (closure->context, packed); + + g_ptr_array_free (packed, TRUE); + favourite_contact_closure_free (closure); + + if (action_chain != NULL) + tpl_actionchain_continue (action_chain); +} + + +static void +tpl_dbus_service_get_favourite_contacts (TplSvcLogger *self, + DBusGMethodInvocation *context) +{ + TplDBusServicePriv *priv; + FavouriteContactClosure *closure; + + g_return_if_fail (TPL_IS_DBUS_SERVICE (self)); + g_return_if_fail (context != NULL); + + priv = GET_PRIV (self); + + closure = favourite_contact_closure_new (TPL_DBUS_SERVICE (self), NULL, NULL, + context); + + /* If we're still waiting on the contacts to finish being parsed from disk, + * queue this action */ + if (priv->favourite_contacts_actions != NULL) + { + tpl_actionchain_append (priv->favourite_contacts_actions, + pendingproc_get_favourite_contacts, closure); + } + else + pendingproc_get_favourite_contacts (NULL, closure); +} + + +static void +append_favourite_contacts_file_entries (const gchar *account, + GHashTable *contacts, + GString *string) +{ + GList *l; + + for (l = g_hash_table_get_keys (contacts); l; l = g_list_delete_link (l, l)) + g_string_append_printf (string, "%s %s\n", account, (const gchar*) l->data); +} + + +static gchar * +favourite_contacts_to_string (TplDBusService *self) +{ + TplDBusServicePriv *priv = GET_PRIV (self); + GString *string; + + string = g_string_new (""); + + g_hash_table_foreach (priv->accounts_contacts_map, + (GHFunc) append_favourite_contacts_file_entries, string); + + return g_string_free (string, FALSE); +} + + +static void +favourite_contacts_file_replace_contents_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GFile *file = G_FILE (object); + GError *error = NULL; + FavouriteContactClosure *closure = user_data; + gboolean success; + + if (g_file_replace_contents_finish (file, result, NULL, &error)) + { + success = TRUE; + } + else + { + DEBUG ("Failed to save favourite contacts file: %s", error->message); + success = FALSE; + g_clear_error (&error); + } + + ((FavouriteContactCallback) closure->cb) (success, closure); +} + + +static void +favourite_contacts_file_save_async (TplDBusService *self, + FavouriteContactClosure *closure) +{ + gchar *dir; + const gchar *filename; + GFile *file; + gchar *file_contents; + + g_return_if_fail (closure != NULL); + + filename = favourite_contacts_get_filename (); + dir = g_path_get_dirname (filename); + g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR); + g_free (dir); + + file = g_file_new_for_path (filename); + + file_contents = favourite_contacts_to_string (self); + + closure->file_contents = file_contents; + + g_file_replace_contents_async (file, + file_contents, strlen (file_contents), NULL, FALSE, + G_FILE_CREATE_REPLACE_DESTINATION, NULL, + favourite_contacts_file_replace_contents_cb, closure); + + g_object_unref (file); +} + + +static void +add_favourite_contact_file_save_cb (gboolean added_favourite, + FavouriteContactClosure *closure) +{ + TplDBusServicePriv *priv = GET_PRIV (closure->service); + TplActionChain *action_chain = priv->favourite_contacts_actions; + + if (added_favourite) + { + const gchar *added[] = { NULL, NULL }; + const gchar *removed[] = { NULL }; + + added[0] = closure->contact_id; + + tpl_svc_logger_emit_favourite_contacts_changed (closure->service, + closure->account, added, removed); + } + + tpl_svc_logger_return_from_add_favourite_contact (closure->context); + + favourite_contact_closure_free (closure); + if (action_chain != NULL) + tpl_actionchain_continue (action_chain); +} + + +static void +pendingproc_add_favourite_contact (TplActionChain *action_chain, + gpointer user_data) +{ + FavouriteContactClosure *closure = user_data; + gboolean should_add = FALSE; + TplDBusServicePriv *priv; + GError *error = NULL; + + g_return_if_fail (closure); + g_return_if_fail (TPL_IS_DBUS_SERVICE (closure->service)); + g_return_if_fail (closure->context != NULL); + + priv = GET_PRIV (closure->service); + + if (!tp_dbus_check_valid_object_path (closure->account, &error)) + { + dbus_g_method_return_error (closure->context, error); + + goto pendingproc_add_favourite_contact_ERROR; + } + + should_add = favourite_contacts_add_entry (closure->service, closure->account, + closure->contact_id); + + closure->cb = add_favourite_contact_file_save_cb; + + if (should_add) + favourite_contacts_file_save_async (closure->service, closure); + else + add_favourite_contact_file_save_cb (FALSE, closure); + + return; + +pendingproc_add_favourite_contact_ERROR: + g_clear_error (&error); + if (action_chain != NULL) + tpl_actionchain_terminate (action_chain); +} + + +static void +tpl_dbus_service_add_favourite_contact (TplSvcLogger *self, + const gchar *account, + const gchar *contact_id, + DBusGMethodInvocation *context) +{ + TplDBusServicePriv *priv; + FavouriteContactClosure *closure; + + g_return_if_fail (TPL_IS_DBUS_SERVICE (self)); + g_return_if_fail (context != NULL); + + priv = GET_PRIV (self); + + closure = favourite_contact_closure_new (TPL_DBUS_SERVICE (self), account, + contact_id, context); + + /* If we're still waiting on the contacts to finish being parsed from disk, + * queue this action */ + if (priv->favourite_contacts_actions != NULL) + { + tpl_actionchain_append (priv->favourite_contacts_actions, + pendingproc_add_favourite_contact, closure); + } + else + pendingproc_add_favourite_contact (NULL, closure); +} + +static void +remove_favourite_contact_file_save_cb (gboolean removed_favourite, + FavouriteContactClosure *closure) +{ + TplDBusServicePriv *priv = GET_PRIV (closure->service); + TplActionChain *action_chain = priv->favourite_contacts_actions; + + if (removed_favourite) + { + const gchar *added[] = { NULL }; + const gchar *removed[] = { NULL, NULL }; + + removed[0] = closure->contact_id; + + tpl_svc_logger_emit_favourite_contacts_changed (closure->service, + closure->account, added, removed); + } + + tpl_svc_logger_return_from_remove_favourite_contact (closure->context); + + favourite_contact_closure_free (closure); + if (action_chain != NULL) + tpl_actionchain_continue (action_chain); +} + + +static void +pendingproc_remove_favourite_contact (TplActionChain *action_chain, + gpointer user_data) +{ + FavouriteContactClosure *closure = user_data; + GHashTable *contacts; + gboolean removed = FALSE; + GError *error = NULL; + + g_return_if_fail (closure != NULL); + g_return_if_fail (TPL_IS_DBUS_SERVICE (closure->service)); + g_return_if_fail (closure->context != NULL); + + TplDBusServicePriv *priv = GET_PRIV (closure->service); + + if (!tp_dbus_check_valid_object_path (closure->account, &error)) + { + dbus_g_method_return_error (closure->context, error); + + goto pendingproc_remove_favourite_contact_ERROR; + } + + DEBUG ("removing favourite contact: account '%s', ID '%s'", + closure->account, closure->contact_id); + + contacts = g_hash_table_lookup (priv->accounts_contacts_map, + closure->account); + if (contacts != NULL && g_hash_table_remove (contacts, closure->contact_id)) + removed = TRUE; + + closure->cb = remove_favourite_contact_file_save_cb; + + if (removed) + favourite_contacts_file_save_async (closure->service, closure); + else + remove_favourite_contact_file_save_cb (FALSE, closure); + + return; + +pendingproc_remove_favourite_contact_ERROR: + g_clear_error (&error); + if (action_chain != NULL) + tpl_actionchain_terminate (action_chain); +} + +static void +tpl_dbus_service_remove_favourite_contact (TplSvcLogger *self, + const gchar *account, + const gchar *contact_id, + DBusGMethodInvocation *context) +{ + TplDBusServicePriv *priv; + FavouriteContactClosure *closure; + + g_return_if_fail (TPL_IS_DBUS_SERVICE (self)); + g_return_if_fail (context != NULL); + + priv = GET_PRIV (self); + + closure = favourite_contact_closure_new (TPL_DBUS_SERVICE (self), account, + contact_id, context); + + /* If we're still waiting on the contacts to finish being parsed from disk, + * queue this action */ + if (priv->favourite_contacts_actions != NULL) + { + tpl_actionchain_append (priv->favourite_contacts_actions, + pendingproc_remove_favourite_contact, closure); + } + else + pendingproc_remove_favourite_contact (NULL, closure); +} + +static void tpl_logger_iface_init (gpointer iface, gpointer iface_data) { @@ -216,5 +927,8 @@ tpl_logger_iface_init (gpointer iface, #define IMPLEMENT(x) tpl_svc_logger_implement_##x (klass, tpl_dbus_service_##x) IMPLEMENT (get_recent_messages); + IMPLEMENT (get_favourite_contacts); + IMPLEMENT (add_favourite_contact); + IMPLEMENT (remove_favourite_contact); #undef IMPLEMENT } diff --git a/telepathy-logger/dbus-service.h b/telepathy-logger/dbus-service.h index d0424a2..600c826 100644 --- a/telepathy-logger/dbus-service.h +++ b/telepathy-logger/dbus-service.h @@ -23,6 +23,7 @@ #define __TPL_DBUS_SERVICE_H__ #include <glib-object.h> +#include <telepathy-glib/dbus-properties-mixin.h> #include <telepathy-logger/log-manager.h> @@ -44,7 +45,10 @@ G_BEGIN_DECLS "tpl-dbus-service-error-quark") typedef enum { - TPL_DBUS_SERVICE_ERROR_FAILED + TPL_DBUS_SERVICE_ERROR_FAILED, + /* >= 1 argument(s) is/are invalid */ + TPL_DBUS_SERVICE_ERROR_INVALID_ARGS, + TPL_DBUS_SERVICE_ERROR_NOT_READY, } TplDBusServiceError; typedef struct _TplDBusServicePriv TplDBusServicePriv; diff --git a/tests/tpl-channel-test.c b/tests/tpl-channel-test.c index 31080a1..e87c154 100644 --- a/tests/tpl-channel-test.c +++ b/tests/tpl-channel-test.c @@ -32,7 +32,8 @@ static void call_when_ready_wrapper (TplChannel *tpl_chan, GAsyncReadyCallback cb, gpointer user_data); -static void pendingproc_prepare_tpl_channel (TplActionChain *ctx); +static void pendingproc_prepare_tpl_channel (TplActionChain *ctx, + gpointer user_data); static void got_tpl_chan_ready_cb (GObject *obj, GAsyncResult *result, gpointer user_data); @@ -133,14 +134,15 @@ tpl_channel_test_call_when_ready (TplChannelTest *self, * are unreferenced by g_object_unref: after the order change, it might * happend that an object still has to be created after the change */ actions = tpl_actionchain_new (G_OBJECT (self), cb, user_data); - tpl_actionchain_append (actions, pendingproc_prepare_tpl_channel); + tpl_actionchain_append (actions, pendingproc_prepare_tpl_channel, NULL); /* start the queue consuming */ tpl_actionchain_continue (actions); } static void -pendingproc_prepare_tpl_channel (TplActionChain *ctx) +pendingproc_prepare_tpl_channel (TplActionChain *ctx, + gpointer user_data) { TplChannel *tpl_chan = TPL_CHANNEL (tpl_actionchain_get_object (ctx)); |