/* * Copyright (C) 2003-2007 Imendio AB * Copyright (C) 2007-2011 Collabora Ltd. * Copyright (C) 2012 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * Authors: Xavier Claessens * Cosimo Alfarano */ #include "config.h" #include "log-manager.h" #include "log-manager-internal.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEBUG_FLAG TPL_DEBUG_LOG_MANAGER #include #include #include /** * SECTION:log-manager * @title: TplLogManager * @short_description: Fetch and search through logs * * The #TplLogManager object allows user to fetch logs and make searches. */ /** * TplLogManager: * * An object used to access logs */ /** * TplLogEventFilter: * @event: the #TplEvent to filter * @user_data: user-supplied data * * Returns: %TRUE if @event should appear in the result */ /** * TPL_LOG_MANAGER_ERROR: * * The error domain for the #TplLogManager. */ /* This macro is used to check if a list has been taken by a _finish() * function call. It detects the marker set by _take_list() method. Those * are used to avoid copying the full list on every call. */ #define _LIST_TAKEN(l) ((l) != NULL && (l)->data == NULL) typedef struct { TplConf *conf; GList *stores; GList *writable_stores; GList *readable_stores; } TplLogManagerPriv; typedef void (*TplLogManagerFreeFunc) (gpointer *data); typedef struct { TpAccount *account; TplEntity *target; gint type_mask; GDate *date; guint num_events; TplLogEventFilter filter; gchar *search_text; gpointer user_data; TplEvent *logevent; } TplLogManagerEventInfo; typedef struct { TplLogManager *manager; TplLogManagerEventInfo *request; TplLogManagerFreeFunc request_free; GAsyncReadyCallback cb; gpointer user_data; } TplLogManagerAsyncData; G_DEFINE_TYPE (TplLogManager, tpl_log_manager, G_TYPE_OBJECT); G_DEFINE_BOXED_TYPE (TplLogSearchHit, _tpl_log_manager_search_hit, _tpl_log_manager_search_hit_copy, _tpl_log_manager_search_hit_free); static TplLogManager *manager_singleton = NULL; static void log_manager_finalize (GObject *object) { TplLogManagerPriv *priv; priv = TPL_LOG_MANAGER (object)->priv; g_object_unref (priv->conf); g_list_foreach (priv->stores, (GFunc) g_object_unref, NULL); g_list_free (priv->stores); /* no unref needed here, the only reference kept is in priv->stores */ g_list_free (priv->writable_stores); g_list_free (priv->readable_stores); G_OBJECT_CLASS (tpl_log_manager_parent_class)->finalize (object); } /* * - Singleton LogManager constructor - * Initialises LogStores with LogStoreEmpathy instance */ static GObject * log_manager_constructor (GType type, guint n_props, GObjectConstructParam *props) { GObject *retval = NULL; if (G_LIKELY (manager_singleton)) retval = g_object_ref (manager_singleton); else { retval = G_OBJECT_CLASS (tpl_log_manager_parent_class)->constructor ( type, n_props, props); if (retval == NULL) return NULL; manager_singleton = TPL_LOG_MANAGER (retval); g_object_add_weak_pointer (retval, (gpointer *) &manager_singleton); } return retval; } static void tpl_log_manager_class_init (TplLogManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructor = log_manager_constructor; object_class->finalize = log_manager_finalize; g_type_class_add_private (object_class, sizeof (TplLogManagerPriv)); } static void add_log_store (TplLogManager *self, TplLogStore *store) { g_return_if_fail (TPL_IS_LOG_STORE (store)); /* set the log store in "testmode" if it supports it and the environment is * currently in test mode */ if (g_object_class_find_property (G_OBJECT_GET_CLASS (store), "testmode")) g_object_set (store, "testmode", (g_getenv ("TPL_TEST_MODE") != NULL), NULL); if (!_tpl_log_manager_register_log_store (self, store)) CRITICAL ("Failed to register store name=%s", _tpl_log_store_get_name (store)); /* drop the initial ref */ g_object_unref (store); } static void _globally_enabled_changed (TplConf *conf, GParamSpec *pspec, gpointer user_data) { DEBUG ("Logging has been globally %s", _tpl_conf_is_globally_enabled (conf) ? "enabled" : "disabled"); } static GList * _take_list (GList *list) { GList *copy = NULL; if (list != NULL) { copy = g_list_alloc (); memcpy (copy, list, sizeof (GList)); memset (list, 0, sizeof (GList)); } return copy; } static void _list_of_object_free (gpointer data) { GList *lst = data; /* list of GObject */ if (!_LIST_TAKEN (lst)) g_list_foreach (lst, (GFunc) g_object_unref, NULL); g_list_free (lst); } static void _list_of_date_free (gpointer data) { GList *lst = data; /* list of (GDate *) */ if (!_LIST_TAKEN (lst)) g_list_foreach (lst, (GFunc) g_date_free, NULL); g_list_free (lst); } static void tpl_log_manager_init (TplLogManager *self) { TplLogManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TPL_TYPE_LOG_MANAGER, TplLogManagerPriv); self->priv = priv; DEBUG ("Initialising the Log Manager"); priv->conf = _tpl_conf_dup (); g_signal_connect (priv->conf, "notify::globally-enabled", G_CALLBACK (_globally_enabled_changed), NULL); /* The TPL's default read-write logstore */ add_log_store (self, g_object_new (TPL_TYPE_LOG_STORE_XML, NULL)); /* Load by default the Empathy's legacy 'past coversations' LogStore */ add_log_store (self, g_object_new (TPL_TYPE_LOG_STORE_EMPATHY, NULL)); add_log_store (self, g_object_new (TPL_TYPE_LOG_STORE_PIDGIN, NULL)); /* Load the event counting cache */ add_log_store (self, g_object_new (TPL_TYPE_LOG_STORE_SQLITE, NULL)); DEBUG ("Log Manager initialised"); } /** * tpl_log_manager_dup_singleton: * * Returns: (transfer full): a new reference on the log manager */ TplLogManager * tpl_log_manager_dup_singleton (void) { return g_object_new (TPL_TYPE_LOG_MANAGER, NULL); } /* * _tpl_log_manager_add_event: * @manager: the log manager * @event: a TplEvent subclass's instance * @error: the memory location of GError, filled if an error occurs * * It stores @event, sending it to all the writable registered #TplLogStore objects. * (Every TplLogManager is guaranteed to have at least one writable log store.) * * Returns: %TRUE if the event has been successfully added, otherwise %FALSE. */ gboolean _tpl_log_manager_add_event (TplLogManager *manager, TplEvent *event, GError **error) { TplLogManagerPriv *priv; GList *l; gboolean retval = FALSE; TplEntity *target; TpAccount *account; g_return_val_if_fail (error == NULL || *error == NULL, FALSE); g_return_val_if_fail (TPL_IS_LOG_MANAGER (manager), FALSE); g_return_val_if_fail (TPL_IS_EVENT (event), FALSE); priv = manager->priv; if (!_tpl_conf_is_globally_enabled (priv->conf)) { /* ignore event, logging is globally disabled */ return FALSE; } account = tpl_event_get_account (event); /* check whether receiver is in the list of contacts to ignore */ target = tpl_event_get_receiver (event); if (tpl_log_manager_is_disabled_for_entity (manager, account, target)) return FALSE; /* check whether sender is in the list of contacts to ignore */ target = tpl_event_get_sender (event); if (tpl_log_manager_is_disabled_for_entity (manager, account, target)) return FALSE; /* send the event to any writable log store */ for (l = priv->writable_stores; l != NULL; l = g_list_next (l)) { GError *loc_error = NULL; TplLogStore *store = l->data; gboolean result; result = _tpl_log_store_add_event (store, event, &loc_error); if (!result) { CRITICAL ("logstore name=%s: %s. " "Event may not be logged properly.", _tpl_log_store_get_name (store), loc_error != NULL ? loc_error->message : "no error message"); g_clear_error (&loc_error); } /* TRUE if at least one LogStore succeeds */ retval = result || retval; } if (!retval) { CRITICAL ("Failed to write event to all writable LogStores."); g_set_error_literal (error, TPL_LOG_MANAGER_ERROR, TPL_LOG_MANAGER_ERROR_ADD_EVENT, "Non recoverable error occurred during log manager's " "add_event() execution"); } return retval; } /* * _tpl_log_manager_register_log_store: * @self: the log manager * @logstore: a TplLogStore interface implementation * * It registers @logstore into @manager, the log store has to be an * implementation of the TplLogStore interface. * * @logstore has to properly implement all the search/query methods if the * TplLogStore:readable is set to %TRUE. */ gboolean _tpl_log_manager_register_log_store (TplLogManager *self, TplLogStore *logstore) { TplLogManagerPriv *priv = self->priv; const gchar *name = _tpl_log_store_get_name (logstore); GList *l; g_return_val_if_fail (TPL_IS_LOG_MANAGER (self), FALSE); g_return_val_if_fail (TPL_IS_LOG_STORE (logstore), FALSE); /* check that the logstore name is not already used */ for (l = priv->stores; l != NULL; l = g_list_next (l)) { TplLogStore *store = l->data; if (!tp_strdiff (name, _tpl_log_store_get_name (store))) { DEBUG ("name=%s: already registered", name); return FALSE; } } if (_tpl_log_store_is_readable (logstore)) priv->readable_stores = g_list_prepend (priv->readable_stores, logstore); if (_tpl_log_store_is_writable (logstore)) priv->writable_stores = g_list_prepend (priv->writable_stores, logstore); /* reference just once, writable/readable lists are kept in sync with the * general list and never written separately */ priv->stores = g_list_prepend (priv->stores, g_object_ref (logstore)); DEBUG ("LogStore name=%s registered", _tpl_log_store_get_name (logstore)); return TRUE; } /** * tpl_log_manager_exists: * @manager: TplLogManager * @account: TpAccount * @target: a non-NULL #TplEntity * @type_mask: event type filter see #TplEventTypeMask * * Checks if logs exist for @target. * * It applies for any registered TplLogStore with the TplLogStore:readable * property %TRUE. * Returns: %TRUE logs exist for @target, otherwise %FALSE */ gboolean tpl_log_manager_exists (TplLogManager *manager, TpAccount *account, TplEntity *target, gint type_mask) { GList *l; TplLogManagerPriv *priv; g_return_val_if_fail (TPL_IS_LOG_MANAGER (manager), FALSE); g_return_val_if_fail (TPL_IS_ENTITY (target), FALSE); priv = manager->priv; for (l = priv->readable_stores; l != NULL; l = g_list_next (l)) { if (_tpl_log_store_exists (TPL_LOG_STORE (l->data), account, target, type_mask)) return TRUE; } return FALSE; } /* * _tpl_log_manager_get_dates: * @manager: a #TplLogManager * @account: a #TpAccount * @target: a non-NULL #TplEntity * * Retrieves a list of #GDate corresponding to each day * at least an event exist for @target_id. * * It applies for any registered TplLogStore with the TplLogStore:readable * property %TRUE. * * Returns: a GList of (GDate *), to be freed using something like * g_list_free_full (lst, g_date_free); */ GList * _tpl_log_manager_get_dates (TplLogManager *manager, TpAccount *account, TplEntity *target, gint type_mask) { GList *l, *out = NULL; TplLogManagerPriv *priv; g_return_val_if_fail (TPL_IS_LOG_MANAGER (manager), NULL); g_return_val_if_fail (TPL_IS_ENTITY (target), NULL); priv = manager->priv; for (l = priv->readable_stores; l != NULL; l = g_list_next (l)) { TplLogStore *store = TPL_LOG_STORE (l->data); GList *new; /* Insert dates of each store in the out list. Keep the out list sorted * and avoid to insert dups. */ new = _tpl_log_store_get_dates (store, account, target, type_mask); while (new) { if (g_list_find_custom (out, new->data, (GCompareFunc) g_date_compare)) g_date_free (new->data); else out = g_list_insert_sorted (out, new->data, (GCompareFunc) g_date_compare); new = g_list_delete_link (new, new); } } return out; } GList * _tpl_log_manager_get_events_for_date (TplLogManager *manager, TpAccount *account, TplEntity *target, gint type_mask, const GDate *date) { GList *l, *out = NULL; TplLogManagerPriv *priv; g_return_val_if_fail (TPL_IS_LOG_MANAGER (manager), NULL); g_return_val_if_fail (TPL_IS_ENTITY (target), NULL); priv = manager->priv; for (l = priv->readable_stores; l != NULL; l = g_list_next (l)) { TplLogStore *store = TPL_LOG_STORE (l->data); out = g_list_concat (out, _tpl_log_store_get_events_for_date (store, account, target, type_mask, date)); } return out; } GList * _tpl_log_manager_get_filtered_events (TplLogManager *manager, TpAccount *account, TplEntity *target, gint type_mask, guint num_events, TplLogEventFilter filter, gpointer user_data) { TplLogManagerPriv *priv; GQueue out = G_QUEUE_INIT; GList *l; g_return_val_if_fail (TPL_IS_LOG_MANAGER (manager), NULL); g_return_val_if_fail (TPL_IS_ENTITY (target), NULL); priv = manager->priv; /* Get num_events from each log store and keep only the * newest ones in the out list. Keep that list sorted: olders first. */ for (l = priv->readable_stores; l != NULL; l = g_list_next (l)) { TplLogStore *store = TPL_LOG_STORE (l->data); GList *new, *index = NULL; new = _tpl_log_store_get_filtered_events (store, account, target, type_mask, num_events, filter, user_data); while (new != NULL) { index = _tpl_event_queue_insert_sorted_after (&out, index, new->data); if (out.length > num_events) { /* We have too many elements. Remove the oldest event. */ g_object_unref (g_queue_pop_head (&out)); } new = g_list_delete_link (new, new); } } return out.head; } /* * _tpl_log_manager_get_entities: * @manager: the log manager * @account: a TpAccount the query will return data related to * * It queries the readable TplLogStores in @manager for all the buddies the * log store has at least a conversation stored originated using @account. * * Returns: a list of pointer to #TplEntity, to be freed using something like * g_list_free_full (lst, g_object_unref) */ GList * _tpl_log_manager_get_entities (TplLogManager *manager, TpAccount *account) { GList *l, *out = NULL; TplLogManagerPriv *priv; g_return_val_if_fail (TPL_IS_LOG_MANAGER (manager), NULL); g_return_val_if_fail (TP_IS_ACCOUNT (account), NULL); priv = manager->priv; for (l = priv->readable_stores; l != NULL; l = g_list_next (l)) { TplLogStore *store = TPL_LOG_STORE (l->data); GList *in, *j; in = _tpl_log_store_get_entities (store, account); /* merge the lists avoiding duplicates */ for (j = in; j != NULL; j = g_list_next (j)) { TplEntity *entity = TPL_ENTITY (j->data); if (g_list_find_custom (out, entity, (GCompareFunc) _tpl_entity_compare) == NULL) { /* add data if not already present */ out = g_list_prepend (out, entity); } else /* free hit if already present in out */ g_object_unref (entity); } g_list_free (in); } return out; } GList * _tpl_log_manager_search (TplLogManager *manager, const gchar *text, gint type_mask) { GList *l, *out = NULL; TplLogManagerPriv *priv; g_return_val_if_fail (TPL_IS_LOG_MANAGER (manager), NULL); g_return_val_if_fail (!TPL_STR_EMPTY (text), NULL); priv = manager->priv; for (l = priv->readable_stores; l != NULL; l = g_list_next (l)) { TplLogStore *store = TPL_LOG_STORE (l->data); out = g_list_concat (out, _tpl_log_store_search_new (store, text, type_mask)); } return out; } TplLogSearchHit * _tpl_log_manager_search_hit_new (TpAccount *account, TplEntity *target, GDate *date) { TplLogSearchHit *hit = g_slice_new0 (TplLogSearchHit); g_return_val_if_fail (TPL_IS_ENTITY (target), NULL); if (account != NULL) hit->account = g_object_ref (account); hit->target = g_object_ref (target); if (date != NULL) hit->date = g_date_new_dmy (g_date_get_day (date), g_date_get_month (date), g_date_get_year (date)); return hit; } void _tpl_log_manager_search_hit_free (TplLogSearchHit *hit) { if (hit->account != NULL) g_object_unref (hit->account); if (hit->date != NULL) g_date_free (hit->date); if (hit->target != NULL) g_object_unref (hit->target); g_slice_free (TplLogSearchHit, hit); } /** * tpl_log_manager_search_free: (skip) * @hits: a #GList of #TplLogSearchHit * * Free @hits and its content. */ void tpl_log_manager_search_free (GList *hits) { GList *l; for (l = hits; l != NULL; l = g_list_next (l)) { if (l->data != NULL) _tpl_log_manager_search_hit_free (l->data); } g_list_free (hits); } /* start of Async definitions */ static TplLogManagerAsyncData * tpl_log_manager_async_data_new (void) { return g_slice_new0 (TplLogManagerAsyncData); } static void tpl_log_manager_async_data_free (TplLogManagerAsyncData *data) { if (data->manager != NULL) g_object_unref (data->manager); data->request_free ((gpointer) data->request); g_slice_free (TplLogManagerAsyncData, data); } static TplLogManagerEventInfo * tpl_log_manager_event_info_new (void) { return g_slice_new0 (TplLogManagerEventInfo); } static void tpl_log_manager_event_info_free (TplLogManagerEventInfo *data) { tp_clear_object (&data->account); tp_clear_object (&data->logevent); tp_clear_object (&data->target); tp_clear_pointer (&data->date, g_date_free); tp_clear_pointer (&data->search_text, g_free); g_slice_free (TplLogManagerEventInfo, data); } static void _tpl_log_manager_async_operation_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { TplLogManagerAsyncData *async_data = (TplLogManagerAsyncData *) user_data; if (async_data->cb) async_data->cb (G_OBJECT (async_data->manager), result, async_data->user_data); tpl_log_manager_async_data_free (async_data); } void _tpl_log_manager_clear (TplLogManager *self) { GList *l; TplLogManagerPriv *priv; g_return_if_fail (TPL_IS_LOG_MANAGER (self)); priv = self->priv; for (l = priv->stores; l != NULL; l = g_list_next (l)) { _tpl_log_store_clear (TPL_LOG_STORE (l->data)); } } void _tpl_log_manager_clear_account (TplLogManager *self, TpAccount *account) { GList *l; TplLogManagerPriv *priv; g_return_if_fail (TPL_IS_LOG_MANAGER (self)); priv = self->priv; for (l = priv->stores; l != NULL; l = g_list_next (l)) { _tpl_log_store_clear_account (TPL_LOG_STORE (l->data), account); } } void _tpl_log_manager_clear_entity (TplLogManager *self, TpAccount *account, TplEntity *entity) { GList *l; TplLogManagerPriv *priv; g_return_if_fail (TPL_IS_LOG_MANAGER (self)); priv = self->priv; for (l = priv->stores; l != NULL; l = g_list_next (l)) { _tpl_log_store_clear_entity (TPL_LOG_STORE (l->data), account, entity); } } /* There is no g_date_copy() */ static GDate * copy_date (const GDate *date) { return g_date_new_julian (g_date_get_julian (date)); } static void _get_dates_async_thread (GSimpleAsyncResult *simple, GObject *object, GCancellable *cancellable) { TplLogManagerAsyncData *async_data; TplLogManagerEventInfo *event_info; GList *lst = NULL; async_data = g_async_result_get_user_data (G_ASYNC_RESULT (simple)); event_info = async_data->request; lst = _tpl_log_manager_get_dates (async_data->manager, event_info->account, event_info->target, event_info->type_mask); g_simple_async_result_set_op_res_gpointer (simple, lst, _list_of_date_free); } typedef struct { GSimpleAsyncResult *result; GSimpleAsyncThreadFunc func; } AsyncOpData; static AsyncOpData * async_op_data_new (GSimpleAsyncResult *result, GSimpleAsyncThreadFunc func) { AsyncOpData *data = g_slice_new (AsyncOpData); data->result = g_object_ref (result); data->func = func; return data; } static void async_op_data_free (AsyncOpData *data) { g_object_unref (data->result); g_slice_free (AsyncOpData, data); } static void account_prepared_cb (GObject *source, GAsyncResult *result, gpointer user_data) { AsyncOpData *data = user_data; GError *error = NULL; if (!tp_proxy_prepare_finish (source, result, &error)) { g_simple_async_result_take_error (data->result, error); g_simple_async_result_complete (data->result); } else { g_simple_async_result_run_in_thread (data->result, data->func, 0, NULL); } async_op_data_free (data); } static void start_async_op_in_thread (TpAccount *account, GSimpleAsyncResult *result, GSimpleAsyncThreadFunc func) { if (account != NULL) { GQuark features[] = { TP_ACCOUNT_FEATURE_CORE, 0 }; /* Most APIs rely on TpAccount being prepared, so make sure * it is. telepathy-glib is not thread-safe, so we must do * this in the main thread, before starting the actual * operation in the other thread. */ tp_proxy_prepare_async (account, features, account_prepared_cb, async_op_data_new (result, func)); } else { g_simple_async_result_run_in_thread (result, func, 0, NULL); } } /** * tpl_log_manager_get_dates_async: * @manager: a #TplLogManager * @account: a #TpAccount * @target: a non-NULL #TplEntity * @type_mask: event type filter see #TplEventTypeMask * @callback: a callback to call when the request is satisfied * @user_data: data to pass to @callback * * Retrieves a list of #GDate corresponding to each day where * at least one event exist for @target. * * It applies for any registered TplLogStore with the TplLogStore:readable * property %TRUE. */ void tpl_log_manager_get_dates_async (TplLogManager *manager, TpAccount *account, TplEntity *target, gint type_mask, GAsyncReadyCallback callback, gpointer user_data) { TplLogManagerEventInfo *event_info = tpl_log_manager_event_info_new (); TplLogManagerAsyncData *async_data = tpl_log_manager_async_data_new (); GSimpleAsyncResult *simple; g_return_if_fail (TPL_IS_LOG_MANAGER (manager)); g_return_if_fail (TP_IS_ACCOUNT (account)); g_return_if_fail (TPL_IS_ENTITY (target)); event_info->account = g_object_ref (account); event_info->target = g_object_ref (target); event_info->type_mask = type_mask; async_data->manager = g_object_ref (manager); async_data->request = event_info; async_data->request_free = (TplLogManagerFreeFunc) tpl_log_manager_event_info_free; async_data->cb = callback; async_data->user_data = user_data; simple = g_simple_async_result_new (G_OBJECT (manager), _tpl_log_manager_async_operation_cb, async_data, tpl_log_manager_get_dates_async); start_async_op_in_thread (account, simple, _get_dates_async_thread); g_object_unref (simple); } /** * tpl_log_manager_get_dates_finish: * @self: a #TplLogManager * @result: a #GAsyncResult * @dates: (out) (transfer full) (element-type GLib.Date): a pointer to a * #GList used to return the list of #GDate * @error: a #GError to fill * * Returns: #TRUE if the operation was successful, otherwise #FALSE */ gboolean tpl_log_manager_get_dates_finish (TplLogManager *self, GAsyncResult *result, GList **dates, GError **error) { GSimpleAsyncResult *simple; g_return_val_if_fail (TPL_IS_LOG_MANAGER (self), FALSE); g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE); g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self), tpl_log_manager_get_dates_async), FALSE); simple = G_SIMPLE_ASYNC_RESULT (result); if (g_simple_async_result_propagate_error (simple, error)) return FALSE; if (dates != NULL) *dates = _take_list (g_simple_async_result_get_op_res_gpointer (simple)); return TRUE; } static void _get_events_for_date_async_thread (GSimpleAsyncResult *simple, GObject *object, GCancellable *cancellable) { TplLogManagerAsyncData *async_data; TplLogManagerEventInfo *event_info; GList *lst; async_data = g_async_result_get_user_data (G_ASYNC_RESULT (simple)); event_info = async_data->request; lst = _tpl_log_manager_get_events_for_date (async_data->manager, event_info->account, event_info->target, event_info->type_mask, event_info->date); g_simple_async_result_set_op_res_gpointer (simple, lst, _list_of_object_free); } /** * tpl_log_manager_get_events_for_date_async: * @manager: a #TplLogManager * @account: a #TpAccount * @target: a non-NULL #TplEntity * @type_mask: event type filter see #TplEventTypeMask * @date: a #GDate * @callback: a callback to call when the request is satisfied * @user_data: data to pass to @callback * * Retrieve a list of #TplEvent at @date with @target. */ void tpl_log_manager_get_events_for_date_async (TplLogManager *manager, TpAccount *account, TplEntity *target, gint type_mask, const GDate *date, GAsyncReadyCallback callback, gpointer user_data) { TplLogManagerEventInfo *event_info = tpl_log_manager_event_info_new (); TplLogManagerAsyncData *async_data = tpl_log_manager_async_data_new (); GSimpleAsyncResult *simple; g_return_if_fail (TPL_IS_LOG_MANAGER (manager)); g_return_if_fail (TP_IS_ACCOUNT (account)); g_return_if_fail (TPL_IS_ENTITY (target)); g_return_if_fail (date != NULL); event_info->account = g_object_ref (account); event_info->target = g_object_ref (target); event_info->type_mask = type_mask; event_info->date = copy_date (date); async_data->manager = g_object_ref (manager); async_data->request = event_info; async_data->request_free = (TplLogManagerFreeFunc) tpl_log_manager_event_info_free; async_data->cb = callback; async_data->user_data = user_data; simple = g_simple_async_result_new (G_OBJECT (manager), _tpl_log_manager_async_operation_cb, async_data, tpl_log_manager_get_events_for_date_async); start_async_op_in_thread (account, simple, _get_events_for_date_async_thread); g_object_unref (simple); } /** * tpl_log_manager_get_events_for_date_finish: * @self: a #TplLogManager * @result: a #GAsyncResult * @events: (out) (transfer full) (element-type TelepathyLogger.Event): a * pointer to a #GList used to return the list of #TplEvent * @error: a #GError to fill * * Returns: #TRUE if the operation was successful, otherwise #FALSE */ gboolean tpl_log_manager_get_events_for_date_finish (TplLogManager *self, GAsyncResult *result, GList **events, GError **error) { GSimpleAsyncResult *simple; g_return_val_if_fail (TPL_IS_LOG_MANAGER (self), FALSE); g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE); g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self), tpl_log_manager_get_events_for_date_async), FALSE); simple = G_SIMPLE_ASYNC_RESULT (result); if (g_simple_async_result_propagate_error (simple, error)) return FALSE; if (events != NULL) *events = _take_list (g_simple_async_result_get_op_res_gpointer (simple)); return TRUE; } static void _get_filtered_events_async_thread (GSimpleAsyncResult *simple, GObject *object, GCancellable *cancellable) { TplLogManagerAsyncData *async_data; TplLogManagerEventInfo *event_info; GList *lst; async_data = g_async_result_get_user_data (G_ASYNC_RESULT (simple)); event_info = async_data->request; lst = _tpl_log_manager_get_filtered_events (async_data->manager, event_info->account, event_info->target, event_info->type_mask, event_info->num_events, event_info->filter, event_info->user_data); g_simple_async_result_set_op_res_gpointer (simple, lst, _list_of_object_free); } /** * tpl_log_manager_get_filtered_events_async: * @manager: a #TplLogManager * @account: a #TpAccount * @target: a non-NULL #TplEntity * @type_mask: event type filter see #TplEventTypeMask * @num_events: number of maximum events to fetch * @filter: (scope call) (allow-none): an optional filter function * @filter_user_data: user data to pass to @filter * @callback: (scope async) (allow-none): a callback to call when * the request is satisfied * @user_data: data to pass to @callback * * Retrieve the most recent @num_event events exchanged with @target. */ void tpl_log_manager_get_filtered_events_async (TplLogManager *manager, TpAccount *account, TplEntity *target, gint type_mask, guint num_events, TplLogEventFilter filter, gpointer filter_user_data, GAsyncReadyCallback callback, gpointer user_data) { TplLogManagerEventInfo *event_info = tpl_log_manager_event_info_new (); TplLogManagerAsyncData *async_data = tpl_log_manager_async_data_new (); GSimpleAsyncResult *simple; g_return_if_fail (TPL_IS_LOG_MANAGER (manager)); g_return_if_fail (TP_IS_ACCOUNT (account)); g_return_if_fail (TPL_IS_ENTITY (target)); g_return_if_fail (num_events > 0); event_info->account = g_object_ref (account); event_info->target = g_object_ref (target); event_info->type_mask = type_mask; event_info->num_events = num_events; event_info->filter = filter; event_info->user_data = filter_user_data; async_data->manager = g_object_ref (manager); async_data->request = event_info; async_data->request_free = (TplLogManagerFreeFunc) tpl_log_manager_event_info_free; async_data->cb = callback; async_data->user_data = user_data; simple = g_simple_async_result_new (G_OBJECT (manager), _tpl_log_manager_async_operation_cb, async_data, tpl_log_manager_get_filtered_events_async); start_async_op_in_thread (account, simple, _get_filtered_events_async_thread); g_object_unref (simple); } /** * tpl_log_manager_get_filtered_events_finish: * @self: a #TplLogManager * @result: a #GAsyncResult * @events: (out) (transfer full) (element-type TelepathyLogger.Event): * a pointer to a #GList used to return the list #TplEvent * @error: a #GError to fill * * Returns: #TRUE if the operation was successful, otherwise #FALSE. */ gboolean tpl_log_manager_get_filtered_events_finish (TplLogManager *self, GAsyncResult *result, GList **events, GError **error) { GSimpleAsyncResult *simple; g_return_val_if_fail (TPL_IS_LOG_MANAGER (self), FALSE); g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE); g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self), tpl_log_manager_get_filtered_events_async), FALSE); simple = G_SIMPLE_ASYNC_RESULT (result); if (g_simple_async_result_propagate_error (simple, error)) return FALSE; if (events != NULL) *events = _take_list (g_simple_async_result_get_op_res_gpointer (simple)); return TRUE; } /** * tpl_log_manager_walk_filtered_events: * @manager: a #TplLogManager * @account: a #TpAccount * @target: a non-NULL #TplEntity * @type_mask: event type filter see #TplEventTypeMask * @filter: (scope call) (allow-none): an optional filter function * @filter_data: user data to pass to @filter * * Create a #TplLogWalker to traverse all the events exchanged with @target. * Returns: (transfer full): a #TplLogWalker */ TplLogWalker * tpl_log_manager_walk_filtered_events (TplLogManager *manager, TpAccount *account, TplEntity *target, gint type_mask, TplLogEventFilter filter, gpointer filter_data) { TplLogManagerPriv *priv; TplLogWalker *walker; GList *l; g_return_val_if_fail (TPL_IS_LOG_MANAGER (manager), NULL); g_return_val_if_fail (TPL_IS_ENTITY (target), NULL); priv = manager->priv; walker = tpl_log_walker_new (filter, filter_data); for (l = priv->readable_stores; l != NULL; l = g_list_next (l)) { TplLogStore *store = TPL_LOG_STORE (l->data); TplLogIter *iter; iter = _tpl_log_store_create_iter (store, account, target, type_mask); if (iter != NULL) tpl_log_walker_add_iter (walker, iter); } return walker; } static void _get_entities_async_thread (GSimpleAsyncResult *simple, GObject *object, GCancellable *cancellable) { TplLogManagerAsyncData *async_data; TplLogManagerEventInfo *event_info; GList *lst; async_data = g_async_result_get_user_data (G_ASYNC_RESULT (simple)); event_info = async_data->request; lst = _tpl_log_manager_get_entities (async_data->manager, event_info->account); g_simple_async_result_set_op_res_gpointer (simple, lst, _list_of_object_free); } /** * tpl_log_manager_get_entities_async: * @self: a #TplLogManager * @account: a #TpAccount * @callback: a callback to call when the request is satisfied * @user_data: data to pass to @callback * * Start a query looking for all entities for which you have logs in the @account. */ void tpl_log_manager_get_entities_async (TplLogManager *self, TpAccount *account, GAsyncReadyCallback callback, gpointer user_data) { TplLogManagerEventInfo *event_info = tpl_log_manager_event_info_new (); TplLogManagerAsyncData *async_data = tpl_log_manager_async_data_new (); GSimpleAsyncResult *simple; g_return_if_fail (TPL_IS_LOG_MANAGER (self)); g_return_if_fail (TP_IS_ACCOUNT (account)); event_info->account = g_object_ref (account); async_data->manager = g_object_ref (self); async_data->request = event_info; async_data->request_free = (TplLogManagerFreeFunc) tpl_log_manager_event_info_free; async_data->cb = callback; async_data->user_data = user_data; simple = g_simple_async_result_new (G_OBJECT (self), _tpl_log_manager_async_operation_cb, async_data, tpl_log_manager_get_entities_async); start_async_op_in_thread (account, simple, _get_entities_async_thread); g_object_unref (simple); } /** * tpl_log_manager_get_entities_finish: * @self: a #TplLogManager * @result: a #GAsyncResult * @entities: (out) (transfer full) (element-type TelepathyLogger.Entity): a * pointer to a #GList used to return the list of #TplEntity, to be freed * using something like g_list_free_full (lst, g_object_unref) * @error: a #GError to fill * * Returns: #TRUE if the operation was successful, otherwise #FALSE */ gboolean tpl_log_manager_get_entities_finish (TplLogManager *self, GAsyncResult *result, GList **entities, GError **error) { GSimpleAsyncResult *simple; g_return_val_if_fail (TPL_IS_LOG_MANAGER (self), FALSE); g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE); g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self), tpl_log_manager_get_entities_async), FALSE); simple = G_SIMPLE_ASYNC_RESULT (result); if (g_simple_async_result_propagate_error (simple, error)) return FALSE; if (entities != NULL) *entities = _take_list (g_simple_async_result_get_op_res_gpointer (simple)); return TRUE; } static void _search_async_thread (GSimpleAsyncResult *simple, GObject *object, GCancellable *cancellable) { TplLogManagerAsyncData *async_data; TplLogManagerEventInfo *event_info; GList *lst; async_data = g_async_result_get_user_data (G_ASYNC_RESULT (simple)); event_info = async_data->request; lst = _tpl_log_manager_search (async_data->manager, event_info->search_text, event_info->type_mask); g_simple_async_result_set_op_res_gpointer (simple, lst, (GDestroyNotify) tpl_log_manager_search_free); } /** * tpl_log_manager_search_async: * @manager: a #TplLogManager * @text: the pattern to search * @type_mask: event type filter see #TplEventTypeMask * @callback: a callback to call when the request is satisfied * @user_data: data to pass to @callback * * Search for all the conversations containing @text. */ void tpl_log_manager_search_async (TplLogManager *manager, const gchar *text, gint type_mask, GAsyncReadyCallback callback, gpointer user_data) { TplLogManagerEventInfo *event_info = tpl_log_manager_event_info_new (); TplLogManagerAsyncData *async_data = tpl_log_manager_async_data_new (); GSimpleAsyncResult *simple; g_return_if_fail (TPL_IS_LOG_MANAGER (manager)); event_info->search_text = g_strdup (text); event_info->type_mask = type_mask; async_data->manager = g_object_ref (manager); async_data->request = event_info; async_data->request_free = (TplLogManagerFreeFunc) tpl_log_manager_event_info_free; async_data->cb = callback; async_data->user_data = user_data; simple = g_simple_async_result_new (G_OBJECT (manager), _tpl_log_manager_async_operation_cb, async_data, tpl_log_manager_search_async); start_async_op_in_thread (NULL, simple, _search_async_thread); g_object_unref (simple); } /** * tpl_log_manager_search_finish: * @self: a #TplLogManager * @result: a #GAsyncResult * @hits: (out) (transfer full) (element-type TelepathyLogger.LogSearchHit): a * pointer to a #GList used to return the list of #TplLogSearchHit * @error: a #GError to fill * * Returns: #TRUE if the operation was successful, otherwise #FALSE */ gboolean tpl_log_manager_search_finish (TplLogManager *self, GAsyncResult *result, GList **hits, GError **error) { GSimpleAsyncResult *simple; g_return_val_if_fail (TPL_IS_LOG_MANAGER (self), FALSE); g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE); g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self), tpl_log_manager_search_async), FALSE); simple = G_SIMPLE_ASYNC_RESULT (result); if (g_simple_async_result_propagate_error (simple, error)) return FALSE; if (hits != NULL) *hits = _take_list (g_simple_async_result_get_op_res_gpointer (simple)); return TRUE; } /** * tpl_log_manager_errors_quark: * * Returns: the #GQuark associated with the error domain of #TplLogManager */ GQuark tpl_log_manager_errors_quark (void) { static gsize quark = 0; if (g_once_init_enter (&quark)) { GQuark domain = g_quark_from_static_string ( "tpl_log_manager_errors"); g_once_init_leave (&quark, domain); } return (GQuark) quark; } TplLogSearchHit * _tpl_log_manager_search_hit_copy (TplLogSearchHit *hit) { return _tpl_log_manager_search_hit_new (hit->account, hit->target, hit->date); } static gchar * _tpl_log_manager_build_identifier (TpAccount *account, TplEntity *entity) { gchar *identifier; const gchar *acc_name = tp_proxy_get_object_path (account); if (g_str_has_prefix (acc_name, TP_ACCOUNT_OBJECT_PATH_BASE)) acc_name += strlen (TP_ACCOUNT_OBJECT_PATH_BASE); identifier = g_strconcat (acc_name, "/", tpl_entity_get_identifier (entity), NULL); return identifier; } static gboolean _tpl_log_manager_is_disabled_for_entity (TplLogManager *self, const gchar *identifier) { gint i; TplLogManagerPriv *priv = self->priv; const gchar **ignorelist; priv = self->priv; ignorelist = _tpl_conf_get_ignorelist (priv->conf); for (i = 0; ignorelist && ignorelist[i]; i++) { if (g_strcmp0 (ignorelist[i], identifier) == 0) { return TRUE; } } return FALSE; } /** * tpl_log_manager_disable_for_entity: * @self: the log manager * @entity a TplEntity * * Disables logging of events for given entity. By default logging is enabled * for all entities. */ void tpl_log_manager_disable_for_entity (TplLogManager *self, TpAccount *account, TplEntity *entity) { TplLogManagerPriv *priv; gchar *identifier; g_return_if_fail (TPL_IS_LOG_MANAGER (self)); g_return_if_fail (TP_IS_ACCOUNT (account)); g_return_if_fail (TPL_IS_ENTITY (entity)); priv = self->priv; identifier = _tpl_log_manager_build_identifier (account, entity); if (!_tpl_log_manager_is_disabled_for_entity (self, identifier)) { const gchar **ignorelist = _tpl_conf_get_ignorelist (priv->conf); gchar **newlist; if (ignorelist) { gint newlen; newlist = g_strdupv ((gchar **) ignorelist); newlen = g_strv_length (newlist) + 1; newlist = g_realloc (newlist, sizeof (gchar *) * newlen ); newlist[newlen - 1] = g_strdup (identifier); } else { newlist = g_malloc0_n (2, sizeof (gchar *)); newlist[0] = g_strdup (identifier); } _tpl_conf_set_ignorelist (priv->conf, (const gchar **) newlist); g_strfreev (newlist); } g_free (identifier); } /** * tpl_log_manager_enable_for_entity: * @self: the log manager * @entity: a TplEntity * * Re-enables logging of events for entity previously disabled by * tpl_log_manager_disable_for_entity(). By default logging is enabled for all * entities. */ void tpl_log_manager_enable_for_entity (TplLogManager *self, TpAccount *account, TplEntity *entity) { TplLogManagerPriv *priv; gchar *identifier; g_return_if_fail (TPL_IS_LOG_MANAGER (self)); g_return_if_fail (TP_IS_ACCOUNT (account)); g_return_if_fail (TPL_IS_ENTITY (entity)); priv = self->priv; identifier = _tpl_log_manager_build_identifier (account, entity); if (_tpl_log_manager_is_disabled_for_entity (self, identifier)) { gint i, j; const gchar **ignorelist = _tpl_conf_get_ignorelist (priv->conf); gchar **newlist; if (!ignorelist) return; newlist = g_malloc0_n (g_strv_length ((gchar **) ignorelist) - 1, sizeof (gchar *)); j = 0; for (i = 0; ignorelist && ignorelist[i]; i++) { if (g_strcmp0 (ignorelist[i], identifier) != 0) { newlist[j] = g_strdup (ignorelist[i]); j++; } } _tpl_conf_set_ignorelist (priv->conf, (const gchar **) newlist); g_strfreev (newlist); } g_free (identifier); } /** * tpl_log_manager_is_disabled_for_entity: * @self: the log manager * @entity: a TplEntity * * Checks, whether logging is disabled for given entity. By default, logging * is enabled for all entities. * * Returns: %TRUE if logging for the entity has been disabled, %FALSE otherwise. */ gboolean tpl_log_manager_is_disabled_for_entity (TplLogManager *self, TpAccount *account, TplEntity *entity) { gboolean is_disabled; gchar *identifier; g_return_val_if_fail (TPL_IS_LOG_MANAGER (self), FALSE); g_return_val_if_fail (TP_IS_ACCOUNT (account), FALSE); g_return_val_if_fail (TPL_IS_ENTITY (entity), FALSE); identifier = _tpl_log_manager_build_identifier (account, entity); is_disabled = _tpl_log_manager_is_disabled_for_entity (self, identifier); g_free (identifier); return is_disabled; }