/soc/2015/igor.gajowiak/chatlog: 227f25d5bef9: Implemented searc...

Igor Gajowiak igor.gajowiak at gmail.com
Tue Aug 11 17:29:23 EDT 2015


Changeset: 227f25d5bef9715bf5d16b9950b0d2cd3344e2cc
Author:	 Igor Gajowiak <igor.gajowiak at gmail.com>
Date:	 2015-08-11 23:29 +0200
Branch:	 default
URL: https://hg.pidgin.im/soc/2015/igor.gajowiak/chatlog/rev/227f25d5bef9

Description:

Implemented searching for messages containing given phrase.

diffstat:

 libpurple/genericlog.c            |   11 +
 libpurple/genericlog.h            |   28 ++++
 libpurple/plugins/log/logsqlite.c |   66 ++++++++++
 pidgin/gtkgenericlog.c            |  246 ++++++++++++++++++++++++++++++++++---
 pidgin/gtkgenericlog.h            |   12 +
 5 files changed, 338 insertions(+), 25 deletions(-)

diffs (truncated from 636 to 300 lines):

diff --git a/libpurple/genericlog.c b/libpurple/genericlog.c
--- a/libpurple/genericlog.c
+++ b/libpurple/genericlog.c
@@ -311,6 +311,16 @@ purple_genericlog_get_older_msgs(PurpleB
 }
 
 gboolean
+purple_genericlog_find_msgs(PurpleBlistNode *contact, const gchar* pattern,
+	guint limit, guint offset, GList** result, GError** error)
+{
+	DEFINE_GENERICLOG_FUNC_WITH_RETURN(find_msgs,
+		"%s does not support searching messages",
+		"Searching messages from %s failed",
+		contact, pattern, limit, offset, result);
+}
+
+gboolean
 purple_genericlog_get_all_contacts(GList** result, GError** error)
 {
 	DEFINE_GENERICLOG_FUNC_WITH_RETURN(get_all_contacts,
@@ -566,6 +576,7 @@ purple_genericlog_class_init(PurpleGener
 	klass->get_all_contacts = NULL;
 	klass->get_all_days = NULL;
 	klass->wipe_log_for_contact = NULL;
+	klass->find_msgs = NULL;
 	klass->init_export = NULL;
 	klass->read_chunk = NULL;
 	klass->finalize_export = NULL;
diff --git a/libpurple/genericlog.h b/libpurple/genericlog.h
--- a/libpurple/genericlog.h
+++ b/libpurple/genericlog.h
@@ -74,6 +74,9 @@ struct _PurpleGenericLog
  *                   then upper bound check is skipped. Messages are sorted
  *                   by their timestamp in descending order.
  *                   Returns %TRUE if operation succeeds, %FALSE otherwise.
+ * @find_msgs:       Gets a list of messages sent to or received from a given
+ *                   contact containing given phrase. Messages should be sorted
+ *                   by their timestamp in descending order.
  * @get_all_contacts: Gets a list of all #PurpleBlistNode objects with at least
  *                    one message in the log. Returns %TRUE if operation
  *                    succeeds, %FALSE otherwise. The client is responsible for
@@ -127,6 +130,9 @@ struct _PurpleGenericLogClass
 	gboolean (*get_older_msgs)(PurpleBlistNode *contact, guint64 timestamp,
 		PurpleMessage *message, GList **result);
 
+	gboolean (*find_msgs)(PurpleBlistNode *contact, const gchar* pattern,
+		guint limit, guint offset, GList **result);
+
 	gboolean (*get_all_contacts)(GList **result);
 
 	gboolean (*get_all_days)(PurpleBlistNode *contact, GArray **result);
@@ -354,6 +360,28 @@ purple_genericlog_get_older_msgs(PurpleB
 	PurpleMessage *message, GList **result, GError **error);
 
 /**
+ * purple_genericlog_find_msgs:
+ * @contact:     The contact, must be PurpleChat or PurpleBuddy.
+ * @phrase:      The phrase to search for.
+ * @limit:       The limit of the size of the result.
+ * @offset:      The number of messages to skip.
+ * @result[out]: The return location for the result. Contains objects of
+ *               type #PurpleMessage.
+ * @error:       Return location for a #GError or %NULL. If provided, this
+ *               will be set to the reason if getting messages fails.
+ *
+ * Gets a list of messages sent to or received from a given contact containing
+ * given phrase from the active log.
+ *
+ * Messages are sorted by their timestamp in descending order.
+ *
+ * Returns: %TRUE if succeeds, %FALSE otherwise.
+ */
+gboolean
+purple_genericlog_find_msgs(PurpleBlistNode *contact, const gchar* phrase,
+	guint limit, guint offset, GList **result, GError **error);
+
+/**
  * purple_genericlog_get_all_contacts:
  * @result[out]: Return location for a #GList containing a list of #PurpleBuddy
  *               objects. This will not be set in case of any errors.
diff --git a/libpurple/plugins/log/logsqlite.c b/libpurple/plugins/log/logsqlite.c
--- a/libpurple/plugins/log/logsqlite.c
+++ b/libpurple/plugins/log/logsqlite.c
@@ -216,6 +216,22 @@ typedef enum
 	GET_OLDER_MSGS_UPPER_QUERY_PARAM_EXCEPTID   = 4,
 } GetOlderMsgsUpperQueryParam;
 
+static sqlite3_stmt *find_msgs_q = NULL;
+
+static const char *find_msgs_q_str =
+	"SELECT Id, Author, AuthorAlias, Recipient, Contents, MsgTime, Flags "
+	"FROM Messages "
+	"WHERE ContactId = ?1 AND Contents LIKE '%' || ?2 || '%' "
+	"ORDER BY MsgTime DESC, Id DESC LIMIT ?3 OFFSET ?4;";
+
+typedef enum
+{
+	FIND_MSGS_QUERY_PARAM_CONTACTYID = 1,
+	FIND_MSGS_QUERY_PARAM_PATTERN    = 2,
+	FIND_MSGS_QUERY_PARAM_LIMIT      = 3,
+	FIND_MSGS_QUERY_PARAM_OFFSET     = 4,
+} FindMsgsQueryParam;
+
 typedef enum
 {
 	GET_MSGS_QUERY_COL_ID          = 0,
@@ -306,6 +322,7 @@ sqlitelog_finalize_queries()
 	sqlitelog_finalize_query(&get_all_msgs_q);
 	sqlitelog_finalize_query(&get_older_msgs_q);
 	sqlitelog_finalize_query(&get_older_msgs_upper_q);
+	sqlitelog_finalize_query(&find_msgs_q);
 	sqlitelog_finalize_query(&mark_as_seen_q);
 	sqlitelog_finalize_query(&get_all_contacts_q);
 	sqlitelog_finalize_query(&get_all_days_q);
@@ -354,6 +371,7 @@ sqlitelog_prepare_queries()
 		!sqlitelog_prepare_query(get_older_msgs_q_str, &get_older_msgs_q) ||
 		!sqlitelog_prepare_query(get_older_msgs_upper_q_str,
 			&get_older_msgs_upper_q) ||
+		!sqlitelog_prepare_query(find_msgs_q_str, &find_msgs_q) ||
 		!sqlitelog_prepare_query(mark_as_seen_q_str, &mark_as_seen_q) ||
 		!sqlitelog_prepare_query(get_all_contacts_q_str, &get_all_contacts_q) ||
 		!sqlitelog_prepare_query(get_all_days_q_str, &get_all_days_q) ||
@@ -928,6 +946,37 @@ sqlitelog_get_older_msgs_upper_impl(Purp
 }
 
 static gboolean
+sqlitelog_find_msgs_impl(PurpleBlistNode *contact, const gchar *pattern,
+	guint limit, guint offset, GList **result)
+{
+	g_assert(contact);
+	g_assert(pattern);
+	g_assert(result);
+
+	guint contact_id;
+	if (!sqlitelog_get_contact_id_from_obj(contact, &contact_id))
+		return FALSE;
+
+	if (contact_id == SQLITELOG_DB_ID_NONE) {
+		*result = NULL;
+		return TRUE;
+	}
+
+	if (sqlite3_bind_int64(find_msgs_q, FIND_MSGS_QUERY_PARAM_CONTACTYID,
+			contact_id) != SQLITE_OK ||
+		sqlite3_bind_text(find_msgs_q, FIND_MSGS_QUERY_PARAM_PATTERN,
+			pattern, -1, SQLITE_STATIC) != SQLITE_OK ||
+		sqlite3_bind_int64(find_msgs_q, FIND_MSGS_QUERY_PARAM_LIMIT, limit)
+			!= SQLITE_OK ||
+		sqlite3_bind_int64(find_msgs_q, FIND_MSGS_QUERY_PARAM_OFFSET, offset)
+			!= SQLITE_OK) {
+		return FALSE;
+	}
+
+	return sqlitelog_execute_get_msgs_query(find_msgs_q, result);
+}
+
+static gboolean
 sqlitelog_mark_as_seen_impl(unsigned msg_id)
 {
 	g_assert(msg_id != SQLITELOG_DB_ID_NONE);
@@ -1282,6 +1331,22 @@ sqlitelog_get_older_msgs(PurpleBlistNode
 }
 
 static gboolean
+sqlitelog_find_msgs(PurpleBlistNode *contact, const gchar *pattern, guint limit,
+	guint offset, GList **result)
+{
+	g_return_val_if_fail(contact != NULL, FALSE);
+	g_return_val_if_fail(pattern != NULL, FALSE);
+	g_return_val_if_fail(result != NULL, FALSE);
+
+	if (!sqlitelog_db_handle) {
+		purple_debug_error(SQLITELOG_DEBUG_CATEGORY, "Log is not active");
+		return FALSE;
+	}
+
+	return sqlitelog_find_msgs_impl(contact, pattern, limit, offset, result);
+}
+
+static gboolean
 sqlitelog_get_all_contacts(GList **result)
 {
 	g_return_val_if_fail(result != NULL, FALSE);
@@ -1370,6 +1435,7 @@ purple_sqlitelog_class_init (PurpleSqlit
 	klass->parent_class.get_unseen_msgs = sqlitelog_get_unseen_msgs;
 	klass->parent_class.get_all_msgs = sqlitelog_get_all_msgs;
 	klass->parent_class.get_older_msgs = sqlitelog_get_older_msgs;
+	klass->parent_class.find_msgs = sqlitelog_find_msgs;
 	klass->parent_class.get_all_contacts = sqlitelog_get_all_contacts;
 	klass->parent_class.get_all_days = sqlitelog_get_all_days;
 	klass->parent_class.wipe_log_for_contact =
diff --git a/pidgin/gtkgenericlog.c b/pidgin/gtkgenericlog.c
--- a/pidgin/gtkgenericlog.c
+++ b/pidgin/gtkgenericlog.c
@@ -30,6 +30,7 @@
 #include "gtkwebview.h"
 
 #define GTKGENERICLOG_DEBUG_CATEGORY "gtkgenericlog"
+#define GTKGENERICLOG_SEARCH_CHUNK_SIZE 50
 
 static PidginGenericLogViewer *generic_log_viewer = NULL;
 
@@ -307,10 +308,26 @@ on_reload_item_clicked(GtkAction *action
 		GTK_TREE_MODEL(viewer->blist_tree_store));
 }
 
+static PidginGenericLogMsgFinder*
+create_msg_finder(PidginGenericLogViewer *viewer);
+
+static void
+reset_find_state(PidginGenericLogViewer *viewer);
+
 static void
 on_search_item_clicked(GtkAction *action, gpointer data)
 {
-	printf("on_search_item_clicked\n");
+	g_assert(data);
+
+	PidginGenericLogViewer *viewer = (PidginGenericLogViewer*) data;
+
+	if (!viewer->finder)
+		viewer->finder = create_msg_finder(viewer);
+
+	reset_find_state(viewer);
+
+	gtk_widget_show_all(viewer->finder->window);
+	gtk_widget_grab_focus(viewer->finder->entry);
 }
 
 static void
@@ -348,7 +365,8 @@ on_print_item_clicked(GtkAction *action,
 }
 
 static GtkWidget *
-create_archive_menu_item(PidginGenericLogViewer *viewer)
+create_archive_menu_item(PidginGenericLogViewer *viewer,
+	GtkWidget **search_item_res)
 {
 	g_assert(viewer);
 
@@ -384,6 +402,8 @@ create_archive_menu_item(PidginGenericLo
 	g_signal_connect(G_OBJECT(close_item), "activate",
 		G_CALLBACK(on_close_item_clicked), viewer);
 
+	*search_item_res = search_item;
+
 	return archive_item;
 }
 
@@ -545,13 +565,15 @@ create_contact_menu_item(PidginGenericLo
 static GtkWidget *
 create_menu_bar(PidginGenericLogViewer *viewer,
 	GtkWidget **start_conversation_item_res,
-	GtkWidget **wipe_log_for_contact_item_res)
+	GtkWidget **wipe_log_for_contact_item_res,
+	GtkWidget **search_item_res)
 {
 	g_assert(viewer);
 
 	GtkWidget *menu_bar = gtk_menu_bar_new();
 
-	GtkWidget *archive_item = create_archive_menu_item(viewer);
+	GtkWidget *archive_item = create_archive_menu_item(viewer,
+		search_item_res);
 	gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), archive_item);
 
 	GtkWidget *options_item = create_options_menu_item(viewer);
@@ -569,12 +591,12 @@ update_menu_buttons(PidginGenericLogView
 {
 	g_assert(viewer);
 
-	gboolean start_conv_enabled = TRUE;
-	gboolean wipe_log_enabled = TRUE;
+    gboolean contact_selected = TRUE;
+	gboolean account_enabled = TRUE;
 
 	if (!viewer->current_contact) {
-		start_conv_enabled = FALSE;
-		wipe_log_enabled = FALSE;
+		contact_selected = FALSE;
+		account_enabled = FALSE;
 	}
 	else {
 		PurpleAccount *account = get_account_from_contact(
@@ -582,13 +604,14 @@ update_menu_buttons(PidginGenericLogView
 
 		/* Disable start conversation button if account is disabled */
 		if (!account || !purple_account_get_connection(account))
-			start_conv_enabled = FALSE;
+			account_enabled = FALSE;
 	}
 
 	gtk_widget_set_sensitive(viewer->start_conversation_item,
-		start_conv_enabled);
+		contact_selected && account_enabled);
 	gtk_widget_set_sensitive(viewer->wipe_log_for_contact_item,
-		wipe_log_enabled);
+		contact_selected);
+	gtk_widget_set_sensitive(viewer->search_item, contact_selected);
 }



More information about the Commits mailing list