diff --git a/extensions/Logger.xml b/extensions/Logger.xml index a377829..84f65e0 100644 --- a/extensions/Logger.xml +++ b/extensions/Logger.xml @@ -26,8 +26,28 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + + A string identifier that can be used to request a handle for the given + protocol. + + + + + + + Include contacts. + + + + + Include chat rooms (MUCs). + + + + - + The identifier of the contact who originated this message. @@ -53,6 +73,53 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + + + The account associated with the contact. + + + + + + The identifier of the contact. + + + + + + The timestamp of the last message with this contact. + + + + + + The contents of the last message with this contact. + + + + + + + + The account associated with the contact. + + + + + + The identifier of the contact. + + + + + + A weighting for the contact. Normalised to be between 0 and 1. + + + + @@ -61,7 +128,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - + The buddy's identifier for the conversation @@ -113,17 +180,14 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - + The favourite contact's identifier - Add a contact's designation as a favourite. This method may not be - called until the service is ready. See the FavouriteContactsReady signal and FavouriteContactsIsReady property. + Add a contact's designation as a favourite. @@ -135,17 +199,14 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - + The favourite contact's identifier - Remove a contact's designation as a favourite. This method may not be - called until the service is ready. See the FavouriteContactsReady signal and FavouriteContactsIsReady property. + Remove a contact's designation as a favourite. @@ -174,6 +235,82 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + + + The account to return the most frequent contacts for. Or "/" to + return contacts for all accounts. + + + + + Bitwise OR of what identifiers to include in the result + + + + + Recent contacts, sorted as most recent first. + + + + + Request the list of contacts with whom you have had a recent + conversation, sorted most recent first. + + + + + + + The account to return the most frequent contacts for. Or "/" to + return contacts for all accounts. + + + + + Bitwise OR of what identifiers to include in the result + + + + + Frequent contacts, sorted as most frequent first. + + + + + The maximum frequency. + + + + + Calculate a weighted sum of n-messages/days-ago for each contact + and return a list of contacts sorted by the result of this weighted sum. + + Note: the precise weighting scheme is not guaranteed by the + specification and is implementation specific. + + + + + + The frequency of some of the contacts has changed. + + + + + A list of the contacts whos frequency has changed and their new + frequency. + + + + diff --git a/src/test-api.c b/src/test-api.c index f9eb004..1548448 100644 --- a/src/test-api.c +++ b/src/test-api.c @@ -21,6 +21,7 @@ #include +#include #include #include @@ -29,6 +30,7 @@ #include static GMainLoop *mainloop = NULL; +static guint pending_calls = 0; static void last_chats_cb (TpProxy *logger, @@ -37,20 +39,23 @@ last_chats_cb (TpProxy *logger, gpointer userdata, GObject *weak_obj) { + guint i; + + pending_calls--; + /* Just do demonstrate remote exceptions versus regular GError */ if (error != NULL) { g_printerr ("Error: %s\n", error->message); - return; + goto end; } - g_print ("Names on the message bus:\n"); + g_print ("Latest chats:\n"); - for (guint i = 0; i < result->len; ++i) + for (i = 0; i < result->len; ++i) { GValueArray *message_struct; - const gchar *message_body; - const gchar *message_sender; + const gchar *message_body, *message_sender; guint message_timestamp; message_struct = g_ptr_array_index (result, i); @@ -62,11 +67,100 @@ last_chats_cb (TpProxy *logger, message_timestamp = g_value_get_int64 (g_value_array_get_nth (message_struct, 2)); + // tp_value_array_unpack (message_struct, 3, + // &message_sender, + // &message_body, + // &message_timestamp); + g_print ("%d: [%d] from=%s: %s\n", i, message_timestamp, message_sender, message_body); } - g_main_loop_quit (mainloop); +end: + if (pending_calls == 0) + g_main_loop_quit (mainloop); +} + +static void +recent_contacts (TpProxy *logger, + const GPtrArray *contacts, + const GError *error, + gpointer user_data, + GObject *weak_obj) +{ + guint i; + + pending_calls--; + + if (error != NULL) + { + g_printerr ("Error: %s\n", error->message); + goto end; + } + + g_print ("Recent contacts:\n"); + + for (i = 0; i < contacts->len; i++) + { + GValueArray *array = g_ptr_array_index (contacts, i); + const char *account_path, *identifier, *last; + gint64 timestamp; + + tp_value_array_unpack (array, 4, + &account_path, + &identifier, + ×tamp, + &last); + + g_print (" - %s ::\n %s (%" G_GINT64_FORMAT ")\n > %s\n", + account_path + strlen (TP_ACCOUNT_OBJECT_PATH_BASE), + identifier, timestamp, last); + } + +end: + if (pending_calls == 0) + g_main_loop_quit (mainloop); +} + +static void +frequent_contacts (TpProxy *logger, + const GPtrArray *contacts, + double maxfreq, + const GError *error, + gpointer user_data, + GObject *weak_obj) +{ + guint i; + + pending_calls--; + + if (error != NULL) + { + g_printerr ("Error: %s\n", error->message); + goto end; + } + + g_print ("Frequent contacts:\n"); + + for (i = 0; i < contacts->len; i++) + { + GValueArray *array = g_ptr_array_index (contacts, i); + const char *account_path, *identifier; + double freq; + + tp_value_array_unpack (array, 3, + &account_path, + &identifier, + &freq); + + g_print (" - %s ::\n %s (%g)\n", + account_path + strlen (TP_ACCOUNT_OBJECT_PATH_BASE), + identifier, freq); + } + +end: + if (pending_calls == 0) + g_main_loop_quit (mainloop); } int @@ -105,6 +199,31 @@ main (int argc, char *argv[]) tpl_cli_logger_call_get_recent_messages (proxy, -1, account, identifer, FALSE, 5, last_chats_cb, NULL, NULL, NULL); + pending_calls++; + + tpl_cli_logger_call_get_recent_contacts (proxy, -1, + account, + TPL_IDENTIFIER_FILTER_CONTACT | TPL_IDENTIFIER_FILTER_ROOM, + recent_contacts, NULL, NULL, NULL); + pending_calls++; + + tpl_cli_logger_call_get_frequent_contacts (proxy, -1, + account, + TPL_IDENTIFIER_FILTER_CONTACT | TPL_IDENTIFIER_FILTER_ROOM, + frequent_contacts, NULL, NULL, NULL); + pending_calls++; + + tpl_cli_logger_call_get_recent_contacts (proxy, -1, + "/", + TPL_IDENTIFIER_FILTER_CONTACT | TPL_IDENTIFIER_FILTER_ROOM, + recent_contacts, NULL, NULL, NULL); + pending_calls++; + + tpl_cli_logger_call_get_frequent_contacts (proxy, -1, + "/", + TPL_IDENTIFIER_FILTER_CONTACT | TPL_IDENTIFIER_FILTER_ROOM, + frequent_contacts, NULL, NULL, NULL); + pending_calls++; g_free (account); diff --git a/telepathy-logger/dbus-service.c b/telepathy-logger/dbus-service.c index cb9fd15..46f01f9 100644 --- a/telepathy-logger/dbus-service.c +++ b/telepathy-logger/dbus-service.c @@ -34,6 +34,9 @@ #include #include #include +#include +#include +#include #include @@ -53,6 +56,8 @@ struct _TplDBusServicePriv /* (the set is implemented as a hash table) */ GHashTable *accounts_contacts_map; TplActionChain *favourite_contacts_actions; + + TplLogStore *sqlite_store; }; G_DEFINE_TYPE_WITH_CODE (TplDBusService, _tpl_dbus_service, G_TYPE_OBJECT, @@ -294,6 +299,12 @@ tpl_dbus_service_dispose (GObject *obj) priv->accounts_contacts_map = NULL; } + if (priv->sqlite_store) + { + g_object_unref (priv->sqlite_store); + priv->sqlite_store = NULL; + } + if (priv->favourite_contacts_actions != NULL) priv->favourite_contacts_actions = NULL; @@ -334,6 +345,34 @@ tpl_dbus_service_constructed (GObject *object) static void +_contact_frequency_changed_cb (TplLogStoreSqlite *store, + const char *account_path, + const char *identifier, + double frequency, + TplDBusService *self) +{ + GPtrArray *contacts = g_ptr_array_new_with_free_func ( + (GDestroyNotify) g_value_array_free); + + DEBUG ("frequency changed: %s, %s, %g", + account_path, identifier, frequency); + + // FIXME: need to check the time, after midnight all frequencies need to be + // updated, because they'll all have changed + + g_ptr_array_add (contacts, tp_value_array_build (3, + DBUS_TYPE_G_OBJECT_PATH, account_path, + G_TYPE_STRING, identifier, + G_TYPE_DOUBLE, frequency, + G_TYPE_INVALID)); + + tpl_svc_logger_emit_frequent_contacts_changed (self, contacts); + + g_ptr_array_free (contacts, TRUE); +} + + +static void _tpl_dbus_service_class_init (TplDBusServiceClass *klass) { GObjectClass* object_class = G_OBJECT_CLASS (klass); @@ -358,6 +397,10 @@ _tpl_dbus_service_init (TplDBusService *self) 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; + + priv->sqlite_store = _tpl_log_store_sqlite_dup (); + tp_g_signal_connect_object (priv->sqlite_store, "frequency-changed", + G_CALLBACK (_contact_frequency_changed_cb), self, 0); } @@ -509,7 +552,6 @@ tpl_dbus_service_get_recent_messages (TplSvcLogger *self, DBusGMethodInvocation *context) { TplDBusServicePriv *priv = TPL_DBUS_SERVICE (self)->priv; - TpDBusDaemon *tp_dbus; TpAccount *account; RecentMessagesContext *ctx; GError *error = NULL; @@ -517,15 +559,7 @@ tpl_dbus_service_get_recent_messages (TplSvcLogger *self, g_return_if_fail (TPL_IS_DBUS_SERVICE (self)); g_return_if_fail (context != NULL); - tp_dbus = tp_dbus_daemon_dup (&error); - if (tp_dbus == NULL) - { - DEBUG ("Unable to acquire the bus daemon: %s", error->message); - dbus_g_method_return_error (context, error); - goto out; - } - - account = tp_account_new (tp_dbus, account_path, &error); + account = _tpl_get_tp_account (account_path, &error); if (account == NULL) { DEBUG ("Unable to acquire the account for %s: %s", account_path, @@ -547,13 +581,275 @@ tpl_dbus_service_get_recent_messages (TplSvcLogger *self, _get_dates_return, ctx); out: + g_clear_error (&error); +} + +static int +cmp_recent_contacts (gconstpointer a, + gconstpointer b) +{ + gint64 time1, time2; + + time1 = g_value_get_int64 (g_value_array_get_nth ((GValueArray *) a, 2)); + time2 = g_value_get_int64 (g_value_array_get_nth ((GValueArray *) b, 2)); + + return CLAMP (time2 - time1, -1, 1); +} + +static void +tpl_dbus_service_get_recent_contacts (TplSvcLogger *self, + const char *account_path, + guint filter_flags, + DBusGMethodInvocation *context) +{ + TplLogStore *store = _tpl_log_store_sqlite_dup (); + GPtrArray *array = g_ptr_array_new_with_free_func ( + (GDestroyNotify) g_value_array_free); + TpAccount *account; + GList *accounts = NULL, *ptr, *contacts = NULL; + GError *error = NULL; + + if (!tp_strdiff (account_path, "/")) + { + /* an account path of / means to query all accounts */ + TpAccountManager *am = tp_account_manager_dup (); + + DEBUG ("Looking up frequent contacts for all accounts"); + + /* we assume that the AM is ready */ + if (!tp_account_manager_is_prepared (am, + TP_ACCOUNT_MANAGER_FEATURE_CORE)) + { + error = g_error_new (TP_ERRORS, + TP_ERROR_NOT_AVAILABLE, + "AccountManager doesn't seem to be prepared"); + + DEBUG ("AM not prepared"); + + dbus_g_method_return_error (context, error); + g_error_free (error); + + goto out; + } + + accounts = tp_account_manager_get_valid_accounts (am); + + /* hold a reference, because accounts below holds a reference */ + g_list_foreach (accounts, (GFunc) g_object_ref, NULL); + } + else + { + DEBUG ("Looking up frequent contacts for account %s", account_path); + + account = _tpl_get_tp_account (account_path, &error); + if (account == NULL) + { + DEBUG ("Unable to acquire the account for %s: %s", account_path, + error->message); + + dbus_g_method_return_error (context, error); + goto out; + } + + accounts = g_list_prepend (accounts, account); + + /* N.B. when we created this account, we're holding a reference */ + } - if (tp_dbus != NULL) - g_object_unref (tp_dbus); + for (ptr = accounts; ptr != NULL; ptr = ptr->next) + { + GList *chats, *ptr2; + + account = TP_ACCOUNT (ptr->data); + chats = _tpl_log_store_get_chats (store, account); + + for (ptr2 = chats; ptr2 != NULL; ptr2 = ptr2->next) + { + TplLogSearchHit *hit = ptr2->data; + gint64 date; + + if (!((filter_flags & TPL_IDENTIFIER_FILTER_ROOM && hit->is_chatroom) || + (filter_flags & TPL_IDENTIFIER_FILTER_CONTACT && !hit->is_chatroom) + )) + continue; + + date = _tpl_log_store_sqlite_get_most_recent ( + TPL_LOG_STORE_SQLITE (store), account, hit->chat_id); + + /* create a GValueArray for this contact, and store it in a GList */ + contacts = g_list_insert_sorted (contacts, + tp_value_array_build (2, + DBUS_TYPE_G_OBJECT_PATH, tp_proxy_get_object_path (account), + G_TYPE_STRING, hit->chat_id, + G_TYPE_INT64, date, + G_TYPE_STRING, "FIXME", + G_TYPE_INVALID), + cmp_recent_contacts); + } + + g_list_foreach (chats, (GFunc) _tpl_log_manager_search_hit_free, NULL); + g_list_free (chats); + } + + g_list_free (accounts); + g_list_foreach (accounts, (GFunc) g_object_unref, NULL); + + /* copy from the GList to the GPtrArray */ + for (ptr = contacts; ptr != NULL; ptr = ptr->next) + { + g_ptr_array_add (array, ptr->data); + } + + g_list_free (contacts); + tpl_svc_logger_return_from_get_recent_contacts (context, array); + +out: g_clear_error (&error); + g_object_unref (store); + g_ptr_array_free (array, TRUE); } +static int +cmp_frequent_contacts (gconstpointer a, + gconstpointer b) +{ + double freq1, freq2; + + freq1 = g_value_get_double (g_value_array_get_nth ((GValueArray *) a, 2)); + freq2 = g_value_get_double (g_value_array_get_nth ((GValueArray *) b, 2)); + + if (freq1 == freq2) + return 0; + if (freq1 > freq2) + return -1; + else + return 1; +} + + +static void +tpl_dbus_service_get_frequent_contacts (TplSvcLogger *self, + const char *account_path, + guint flags, + DBusGMethodInvocation *context) +{ + TplLogStore *store = _tpl_log_store_sqlite_dup (); + GPtrArray *array = g_ptr_array_new_with_free_func ( + (GDestroyNotify) g_value_array_free); + TpAccount *account; + GList *accounts = NULL, *contacts = NULL, *ptr; + GError *error = NULL; + double maxfreq = 0; + + if (!tp_strdiff (account_path, "/")) + { + /* an account path of / means to query all accounts */ + TpAccountManager *am = tp_account_manager_dup (); + + DEBUG ("Looking up frequent contacts for all accounts"); + + /* we assume that the AM is ready */ + if (!tp_account_manager_is_prepared (am, + TP_ACCOUNT_MANAGER_FEATURE_CORE)) + { + error = g_error_new (TP_ERRORS, + TP_ERROR_NOT_AVAILABLE, + "AccountManager doesn't seem to be prepared"); + + DEBUG ("AM not prepared"); + + dbus_g_method_return_error (context, error); + g_error_free (error); + + goto out; + } + + accounts = tp_account_manager_get_valid_accounts (am); + + /* hold a reference, because accounts below holds a reference */ + g_list_foreach (accounts, (GFunc) g_object_ref, NULL); + } + else + { + DEBUG ("Looking up frequent contacts for account %s", account_path); + + account = _tpl_get_tp_account (account_path, &error); + if (account == NULL) + { + DEBUG ("Unable to acquire the account for %s: %s", account_path, + error->message); + + dbus_g_method_return_error (context, error); + goto out; + } + + accounts = g_list_prepend (accounts, account); + + /* N.B. when we created this account, we're holding a reference */ + } + + for (ptr = accounts; ptr != NULL; ptr = ptr->next) + { + GList *chats, *ptr2; + + account = TP_ACCOUNT (ptr->data); + chats = _tpl_log_store_get_chats (store, account); + + for (ptr2 = chats; ptr2 != NULL; ptr2 = ptr2->next) + { + TplLogSearchHit *hit = ptr2->data; + double freq; + + if (!((flags & TPL_IDENTIFIER_FILTER_ROOM && hit->is_chatroom) || + (flags & TPL_IDENTIFIER_FILTER_CONTACT && !hit->is_chatroom) + )) + continue; + + freq = _tpl_log_store_sqlite_get_frequency ( + TPL_LOG_STORE_SQLITE (store), + account, hit->chat_id); + + DEBUG ("%s: freq = %g", hit->chat_id, freq); + + /* create a GValueArray for this contact, and store it in a + * GList */ + contacts = g_list_insert_sorted (contacts, + tp_value_array_build (3, + DBUS_TYPE_G_OBJECT_PATH, tp_proxy_get_object_path (account), + G_TYPE_STRING, hit->chat_id, + G_TYPE_DOUBLE, freq, + G_TYPE_INVALID), + cmp_frequent_contacts); + + maxfreq = MAX (maxfreq, freq); + } + + g_list_foreach (chats, + (GFunc) _tpl_log_manager_search_hit_free, NULL); + g_list_free (chats); + } + + g_list_free (accounts); + g_list_foreach (accounts, (GFunc) g_object_unref, NULL); + + DEBUG ("maxfreq = %g", maxfreq); + + /* copy from the GList to the GPtrArray */ + for (ptr = contacts; ptr != NULL; ptr = ptr->next) + { + g_ptr_array_add (array, ptr->data); + } + + g_list_free (contacts); + + tpl_svc_logger_return_from_get_frequent_contacts (context, array, maxfreq); + +out: + g_clear_error (&error); + g_object_unref (store); + g_ptr_array_free (array, TRUE); +} static void append_favourite_contacts_account_and_contacts (const gchar *account, @@ -932,5 +1228,7 @@ tpl_logger_iface_init (gpointer iface, IMPLEMENT (get_favourite_contacts); IMPLEMENT (add_favourite_contact); IMPLEMENT (remove_favourite_contact); + IMPLEMENT (get_recent_contacts); + IMPLEMENT (get_frequent_contacts); #undef IMPLEMENT } diff --git a/telepathy-logger/log-store-sqlite.c b/telepathy-logger/log-store-sqlite.c index d38a8b6..4fa71f2 100644 --- a/telepathy-logger/log-store-sqlite.c +++ b/telepathy-logger/log-store-sqlite.c @@ -31,6 +31,7 @@ #include "entry-text-internal.h" #include "log-store-sqlite-internal.h" #include "log-manager-internal.h" +#include "tpl-marshal.h" #define DEBUG_FLAG TPL_DEBUG_LOG_STORE #include "datetime-internal.h" @@ -63,6 +64,14 @@ enum /* properties */ PROP_WRITABLE }; +enum /* signals */ +{ + FREQUENCY_CHANGED, + LAST_SIGNAL +}; + +static guint _signals[LAST_SIGNAL] = { 0, }; + typedef struct _TplLogStoreSqlitePrivate TplLogStoreSqlitePrivate; struct _TplLogStoreSqlitePrivate { @@ -177,6 +186,15 @@ _tpl_log_store_sqlite_class_init (TplLogStoreSqliteClass *klass) g_object_class_override_property (gobject_class, PROP_READABLE, "readable"); g_object_class_override_property (gobject_class, PROP_WRITABLE, "writable"); + _signals[FREQUENCY_CHANGED] = g_signal_new ("frequency-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + tpl_marshal_VOID__STRING_STRING_DOUBLE, + G_TYPE_NONE, + 3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_DOUBLE); + g_type_class_add_private (gobject_class, sizeof (TplLogStoreSqlitePrivate)); } @@ -554,6 +572,14 @@ tpl_log_store_sqlite_add_message_counter (TplLogStore *self, goto out; } + /* FIXME: keep a cache of frequencies, the new frequency is just + * the old frequency +1 (unless first message after midnight) */ + g_signal_emit (self, _signals[FREQUENCY_CHANGED], 0, + tpl_entry_get_account_path (message), + identifier, + _tpl_log_store_sqlite_get_frequency (TPL_LOG_STORE_SQLITE (self), + tpl_entry_get_account (message), identifier)); + retval = TRUE; out: diff --git a/telepathy-logger/log-store-xml.c b/telepathy-logger/log-store-xml.c index fe6391c..c60bb85 100644 --- a/telepathy-logger/log-store-xml.c +++ b/telepathy-logger/log-store-xml.c @@ -425,7 +425,6 @@ add_message_text_chat (TplLogStoreXml *self, GError **error) { gboolean ret = FALSE; - TpDBusDaemon *bus_daemon; TpAccount *account; TplEntity *sender; const gchar *body_str; @@ -441,13 +440,6 @@ add_message_text_chat (TplLogStoreXml *self, g_return_val_if_fail (TPL_IS_LOG_STORE_XML (self), FALSE); g_return_val_if_fail (TPL_IS_ENTRY_TEXT (message), FALSE); - bus_daemon = tp_dbus_daemon_dup (error); - if (bus_daemon == NULL) - { - DEBUG ("Error acquiring bus daemon: %s", (*error)->message); - goto out; - } - account = tpl_entry_get_account (TPL_ENTRY (message)); body_str = tpl_entry_text_get_message (message); @@ -495,9 +487,6 @@ out: g_free (entry); g_free (avatar_token); - if (bus_daemon != NULL) - g_object_unref (bus_daemon); - return ret; } diff --git a/telepathy-logger/tpl-marshal.list b/telepathy-logger/tpl-marshal.list index 71e2781..9a855d1 100644 --- a/telepathy-logger/tpl-marshal.list +++ b/telepathy-logger/tpl-marshal.list @@ -1 +1 @@ -# add marshallers here +VOID:STRING,STRING,DOUBLE diff --git a/telepathy-logger/util-internal.h b/telepathy-logger/util-internal.h index c8d2479..ecf1c2c 100644 --- a/telepathy-logger/util-internal.h +++ b/telepathy-logger/util-internal.h @@ -24,11 +24,14 @@ #include #include +#include #define TPL_STR_EMPTY(x) ((x) == NULL || (x)[0] == '\0') gchar *_tpl_create_message_token (const gchar *channel, gint64 timestamp, guint msgid); +TpAccount *_tpl_get_tp_account (const char *path, GError **error); + #endif // __TPL_UTIL_H__ diff --git a/telepathy-logger/util.c b/telepathy-logger/util.c index 112c56d..6b04146 100644 --- a/telepathy-logger/util.c +++ b/telepathy-logger/util.c @@ -48,3 +48,21 @@ _tpl_create_message_token (const gchar *channel, return retval; } + +TpAccount * +_tpl_get_tp_account (const char *path, GError **error) +{ + TpDBusDaemon *bus; + TpAccount *account; + + bus = tp_dbus_daemon_dup (error); + + if (error != NULL && *error != NULL) + return NULL; + + account = tp_account_new (bus, path, error); + + g_object_unref (bus); + + return account; +}