[almanah] Bug 647690 — Make search asynchronous



commit cb8ad21e8c32fdce37359e192a7dc3d387af9558
Author: Ãlvaro PeÃa <alvaropg gmail com>
Date:   Mon Jun 27 23:28:41 2011 +0100

    Bug 647690 â Make search asynchronous
    
    Add an asynchronous search API and make use of it in the search dialogue.
    Also give the search dialogue a bit of a facelift.
    
    Closes: bgo#647690

 data/almanah.ui       |  201 +++++++++++++++++++++++++++++++------------------
 src/main-window.c     |    2 +-
 src/search-dialog.c   |  175 ++++++++++++++++++++++++++++++++++++-------
 src/storage-manager.c |  162 +++++++++++++++++++++++++++++++++++++++
 src/storage-manager.h |    7 ++
 5 files changed, 448 insertions(+), 99 deletions(-)
---
diff --git a/data/almanah.ui b/data/almanah.ui
index 27d10b2..031c057 100644
--- a/data/almanah.ui
+++ b/data/almanah.ui
@@ -477,116 +477,172 @@
 
 	<object class="AlmanahSearchDialog" id="almanah_search_dialog">
 		<property name="border-width">5</property>
+		<property name="default_width">400</property>
 		<child internal-child="vbox">
 			<object class="GtkVBox" id="vbox2">
 				<property name="spacing">6</property>
+				<property name="visible">True</property>
 				<child>
 					<object class="GtkVBox" id="almanah_search_dialog_vbox">
 						<property name="spacing">6</property>
-						<property name="border-width">5</property>
+						<property name="border-width">6</property>
+						<property name="visible">True</property>
 						<child>
-							<object class="GtkAlignment" id="alignment0">
-								<property name="bottom-padding">12</property>
+							<object class="GtkHBox" id="hbox0">
+								<property name="spacing">6</property>
+								<property name="visible">True</property>
 								<child>
-									<object class="GtkHBox" id="hbox0">
-										<property name="spacing">6</property>
-										<child>
-											<object class="GtkEntry" id="almanah_sd_search_entry">
-												<property name="activates-default">True</property>
-												<accessibility>
-													<relation target="almanah_sd_search_button" type="labelled-by"/>
-												</accessibility>
-												<child internal-child="accessible">
-													<object class="AtkObject" id="a11y-almanah_sd_search_entry">
-														<property name="AtkObject::accessible-name" translatable="yes">Search entry</property>
-													</object>
-												</child>
-											</object>
-										</child>
-										<child>
-											<object class="GtkButton" id="almanah_sd_search_button">
-												<property name="image">almanah_sd_search_button_image</property>
-												<property name="label" translatable="yes">Search</property>
-												<property name="can-default">True</property>
-												<property name="has-default">True</property>
-												<signal name="clicked" handler="sd_search_button_clicked_cb"/>
-												<accessibility>
-													<relation target="almanah_sd_search_entry" type="label-for"/>
-												</accessibility>
+									<object class="GtkEntry" id="almanah_sd_search_entry">
+										<property name="activates-default">True</property>
+										<property name="visible">True</property>
+										<accessibility>
+											<relation target="almanah_sd_search_button" type="labelled-by"/>
+										</accessibility>
+										<child internal-child="accessible">
+											<object class="AtkObject" id="a11y-almanah_sd_search_entry">
+												<property name="AtkObject::accessible-name" translatable="yes">Search entry</property>
 											</object>
-											<packing>
-												<property name="expand">False</property>
-											</packing>
 										</child>
 									</object>
 								</child>
+								<child>
+									<object class="GtkButton" id="almanah_sd_search_button">
+										<property name="image">almanah_sd_search_button_image</property>
+										<property name="label" translatable="yes">Search</property>
+										<property name="can-default">True</property>
+										<property name="has-default">True</property>
+										<property name="visible">True</property>
+										<signal name="clicked" handler="sd_search_button_clicked_cb"/>
+										<accessibility>
+											<relation target="almanah_sd_search_entry" type="label-for"/>
+										</accessibility>
+									</object>
+									<packing>
+										<property name="expand">False</property>
+									</packing>
+								</child>
+								<child>
+									<object class="GtkButton" id="almanah_sd_cancel_button">
+										<property name="use-stock">True</property>
+										<property name="label">gtk-cancel</property>
+										<property name="can-default">True</property>
+										<property name="has-default">True</property>
+										<property name="sensitive">False</property>
+										<property name="visible">True</property>
+										<signal name="clicked" handler="sd_cancel_button_clicked_cb"/>
+										<accessibility>
+											<relation target="almanah_sd_search_entry" type="label-for"/>
+										</accessibility>
+										</object>
+									<packing>
+										<property name="expand">False</property>
+									</packing>
+								</child>
 							</object>
-							<packing>
-								<property name="expand">False</property>
-							</packing>
-						</child>
-						<child>
-							<object class="GtkLabel" id="almanah_sd_results_label">
-								<property name="label" translatable="yes">Results:</property>
-								<property name="xalign">0</property>
-								<accessibility>
-									<relation target="almanah_sd_results_tree_view" type="label-for"/>
-								</accessibility>
-							</object>
-							<packing>
-								<property name="expand">False</property>
-							</packing>
 						</child>
 						<child>
-							<object class="GtkScrolledWindow" id="scrolledwindow2">
-								<property name="hscrollbar-policy">GTK_POLICY_AUTOMATIC</property>
-								<property name="vscrollbar-policy">GTK_POLICY_AUTOMATIC</property>
-								<property name="shadow-type">GTK_SHADOW_IN</property>
+							<object class="GtkAlignment" id="almanah_sd_results_alignment">
+								<property name="top-padding">12</property>
+								<property name="visible">False</property>
 								<child>
-									<object class="GtkTreeView" id="almanah_sd_results_tree_view">
-										<property name="model">almanah_sd_results_store</property>
-										<property name="headers-visible">False</property>
-										<signal name="row-activated" handler="sd_results_tree_view_row_activated_cb"/>
-										<accessibility>
-											<relation target="almanah_sd_results_label" type="labelled-by"/>
-										</accessibility>
-										<child internal-child="accessible">
-											<object class="AtkObject" id="a11y-almanah_sd_results_tree_view">
-												<property name="AtkObject::accessible-name" translatable="yes">Result List</property>
-											</object>
-										</child>
+									<object class="GtkVBox" id="almanah_sd_results_vbox">
+										<property name="spacing">6</property>
+										<property name="visible">True</property>
 										<child>
-											<object class="GtkTreeViewColumn" id="almanah_sd_results_column_icon">
+											<object class="GtkHBox" id="almanah_sd_results_hbox">
+												<property name="spacing">6</property>
+												<property name="visible">True</property>
 												<child>
-													<object class="GtkCellRendererPixbuf" id="almanah_sd_results_column_icon_renderer"/>
-													<attributes>
-														<attribute name="icon-name">4</attribute>
-													</attributes>
+													<object class="GtkLabel" id="almanah_sd_results_label">
+														<property name="label"></property>
+														<property name="xalign">0</property>
+														<property name="visible">True</property>
+														<accessibility>
+															<relation target="almanah_sd_results_tree_view" type="label-for"/>
+														</accessibility>
+													</object>
+													<packing>
+														<property name="expand">True</property>
+													</packing>
+												</child>
+												<child>
+													<object class="GtkSpinner" id="almanah_sd_search_spinner">
+														<property name="can_focus">False</property>
+														<property name="visible">False</property>
+													</object>
+													<packing>
+														<property name="expand">False</property>
+													</packing>
 												</child>
 											</object>
+											<packing>
+												<property name="expand">False</property>
+											</packing>
 										</child>
 										<child>
-											<object class="GtkTreeViewColumn" id="almanah_sd_results_column_date">
+											<object class="GtkScrolledWindow" id="almanah_sd_results_scrolled">
+												<property name="hscrollbar-policy">GTK_POLICY_AUTOMATIC</property>
+												<property name="vscrollbar-policy">GTK_POLICY_AUTOMATIC</property>
+												<property name="shadow-type">GTK_SHADOW_IN</property>
+												<property name="visible">True</property>
 												<child>
-													<object class="GtkCellRendererText" id="almanah_sd_results_column_date_renderer"/>
-													<attributes>
-														<attribute name="text">3</attribute>
-													</attributes>
+													<object class="GtkTreeView" id="almanah_sd_results_tree_view">
+														<property name="model">almanah_sd_results_store</property>
+														<property name="headers-visible">False</property>
+														<property name="visible">True</property>
+														<signal name="row-activated" handler="sd_results_tree_view_row_activated_cb"/>
+														<accessibility>
+															<relation target="almanah_sd_results_label" type="labelled-by"/>
+														</accessibility>
+														<child internal-child="accessible">
+															<object class="AtkObject" id="a11y-almanah_sd_results_tree_view">
+																<property name="AtkObject::accessible-name" translatable="yes">Result List</property>
+															</object>
+														</child>
+														<child>
+															<object class="GtkTreeViewColumn" id="almanah_sd_results_column_icon">
+																<child>
+																	<object class="GtkCellRendererPixbuf" id="almanah_sd_results_column_icon_renderer"/>
+																	<attributes>
+																		<attribute name="icon-name">4</attribute>
+																	</attributes>
+																</child>
+															</object>
+														</child>
+														<child>
+															<object class="GtkTreeViewColumn" id="almanah_sd_results_column_date">
+																<child>
+																	<object class="GtkCellRendererText" id="almanah_sd_results_column_date_renderer"/>
+																	<attributes>
+																		<attribute name="text">3</attribute>
+																	</attributes>
+																</child>
+															</object>
+														</child>
+													</object>
 												</child>
 											</object>
+											<packing>
+												<property name="expand">True</property>
+											</packing>
 										</child>
 									</object>
 								</child>
 							</object>
+							<packing>
+								<property name="expand">True</property>
+							</packing>
 						</child>
 					</object>
 				</child>
 				<child internal-child="action_area">
 					<object class="GtkHButtonBox" id="hbuttonbox2">
+						<property name="visible">True</property>
 						<child>
 							<object class="GtkButton" id="almanah_sd_view_button">
 								<property name="label" translatable="yes">View Entry</property>
 								<property name="sensitive">False</property>
+								<property name="visible">True</property>
 								<signal name="clicked" handler="sd_view_button_clicked_cb"/>
 							</object>
 						</child>
@@ -594,6 +650,7 @@
 							<object class="GtkButton" id="almanah_sd_close_button">
 								<property name="use-stock">True</property>
 								<property name="label">gtk-close</property>
+								<property name="visible">True</property>
 							</object>
 						</child>
 					</object>
diff --git a/src/main-window.c b/src/main-window.c
index 0ccd056..fc088da 100644
--- a/src/main-window.c
+++ b/src/main-window.c
@@ -945,7 +945,7 @@ mw_search_activate_cb (GtkAction *action, AlmanahMainWindow *main_window)
 
 	gtk_window_set_application (GTK_WINDOW (dialog), gtk_window_get_application (GTK_WINDOW (main_window)));
 	gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (main_window));
-	gtk_widget_show_all (GTK_WIDGET (dialog));
+	gtk_widget_show (GTK_WIDGET (dialog));
 	gtk_dialog_run (GTK_DIALOG (dialog));
 
 	gtk_widget_destroy (GTK_WIDGET (dialog));
diff --git a/src/search-dialog.c b/src/search-dialog.c
index 002b0b2..ba2b881 100644
--- a/src/search-dialog.c
+++ b/src/search-dialog.c
@@ -31,13 +31,23 @@ static void sd_results_selection_changed_cb (GtkTreeSelection *tree_selection, G
 
 /* GtkBuilder callbacks */
 void sd_search_button_clicked_cb (GtkButton *self, AlmanahSearchDialog *search_dialog);
+void sd_cancel_button_clicked_cb (GtkButton *self, AlmanahSearchDialog *search_dialog);
 void sd_results_tree_view_row_activated_cb (GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, AlmanahSearchDialog *self);
 void sd_view_button_clicked_cb (GtkButton *self, AlmanahSearchDialog *search_dialog);
 
+static void sd_search_progress_cb (AlmanahStorageManager *storage_manager, AlmanahEntry *entry, AlmanahSearchDialog **search_dialog_weak_pointer);
+static void sd_search_ready_cb (AlmanahStorageManager *storage_manager, GAsyncResult *res, AlmanahSearchDialog **search_dialog_weak_pointer);
+
 struct _AlmanahSearchDialogPrivate {
 	GtkEntry *sd_search_entry;
+	GtkWidget *sd_search_button;
+	GtkWidget *sd_cancel_button;
+	GtkSpinner *sd_search_spinner;
+	GtkLabel *sd_results_label;
+	GtkWidget *sd_results_alignment;
 	GtkListStore *sd_results_store;
 	GtkTreeSelection *sd_results_selection;
+	GCancellable *sd_cancellable;
 };
 
 G_DEFINE_TYPE (AlmanahSearchDialog, almanah_search_dialog, GTK_TYPE_DIALOG)
@@ -70,6 +80,7 @@ almanah_search_dialog_new (void)
 	const gchar *object_names[] = {
 		"almanah_search_dialog",
 		"almanah_sd_search_button_image",
+		"almanah_sd_cancel_button_image",
 		"almanah_sd_results_store",
 		NULL
 	};
@@ -104,15 +115,22 @@ almanah_search_dialog_new (void)
 
 	priv = ALMANAH_SEARCH_DIALOG (search_dialog)->priv;
 
+	priv->sd_cancellable = NULL;
+
 	/* Grab our child widgets */
 	priv->sd_search_entry = GTK_ENTRY (gtk_builder_get_object (builder, "almanah_sd_search_entry"));
 	priv->sd_results_store = GTK_LIST_STORE (gtk_builder_get_object (builder, "almanah_sd_results_store"));
 	priv->sd_results_selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (gtk_builder_get_object (builder, "almanah_sd_results_tree_view")));
+	priv->sd_results_alignment = GTK_WIDGET (gtk_builder_get_object (builder, "almanah_sd_results_alignment"));
+	priv->sd_search_spinner = GTK_SPINNER (gtk_builder_get_object (builder, "almanah_sd_search_spinner"));
+	priv->sd_results_label = GTK_LABEL (gtk_builder_get_object (builder, "almanah_sd_results_label"));
+	priv->sd_search_button = GTK_WIDGET (gtk_builder_get_object (builder, "almanah_sd_search_button"));
+	priv->sd_cancel_button = GTK_WIDGET (gtk_builder_get_object (builder, "almanah_sd_cancel_button"));
 
 	g_signal_connect (priv->sd_results_selection, "changed", G_CALLBACK (sd_results_selection_changed_cb),
 			  gtk_builder_get_object (builder, "almanah_sd_view_button"));
 
-	gtk_widget_grab_default (GTK_WIDGET (gtk_builder_get_object (builder, "almanah_sd_search_button")));
+	gtk_widget_grab_default (priv->sd_search_button);
 
 	g_object_unref (builder);
 
@@ -129,52 +147,157 @@ static void
 sd_response_cb (GtkDialog *dialog, gint response_id, AlmanahSearchDialog *search_dialog)
 {
 	/* Ensure everything's tidy before we leave the room */
+	if (search_dialog->priv->sd_cancellable != NULL) {
+		g_cancellable_cancel (search_dialog->priv->sd_cancellable);
+	}
+
 	gtk_list_store_clear (search_dialog->priv->sd_results_store);
 	gtk_entry_set_text (search_dialog->priv->sd_search_entry, "");
 
 	gtk_widget_hide (GTK_WIDGET (dialog));
 }
 
+static void
+sd_search_progress_cb (AlmanahStorageManager *storage_manager, AlmanahEntry *entry, AlmanahSearchDialog **search_dialog_weak_pointer)
+{
+	AlmanahSearchDialogPrivate *priv;
+	GDate date;
+	gchar formatted_date[100];
+	GtkTreeIter tree_iter;
+
+	if (*search_dialog_weak_pointer == NULL) {
+		/* The dialogue's been finalised already, so we'd better just return */
+		return;
+	}
+
+	g_return_if_fail (ALMANAH_IS_STORAGE_MANAGER (storage_manager));
+	g_return_if_fail (ALMANAH_IS_ENTRY (entry));
+	g_return_if_fail (ALMANAH_IS_SEARCH_DIALOG (*search_dialog_weak_pointer));
+
+	priv = ALMANAH_SEARCH_DIALOG (*search_dialog_weak_pointer)->priv;
+
+	almanah_entry_get_date (entry, &date);
+	/* Translators: This is a strftime()-format string for the dates displayed in search results. */
+	g_date_strftime (formatted_date, sizeof (formatted_date), _("%A, %e %B %Y"), &date);
+
+	gtk_list_store_append (priv->sd_results_store, &tree_iter);
+	gtk_list_store_set (priv->sd_results_store, &tree_iter,
+	                    0, g_date_get_day (&date),
+	                    1, g_date_get_month (&date),
+	                    2, g_date_get_year (&date),
+	                    3, &formatted_date,
+	                    4, (almanah_entry_is_important (entry) == TRUE) ? "emblem-important" : NULL,
+	                    -1);
+}
+
+static void
+sd_search_ready_cb (AlmanahStorageManager *storage_manager, GAsyncResult *res, AlmanahSearchDialog **search_dialog_weak_pointer)
+{
+	AlmanahSearchDialogPrivate *priv;
+	AlmanahSearchDialog *search_dialog;
+	gint counter;
+	GError *error = NULL;
+
+	/* Finish the operation */
+	counter = almanah_storage_manager_search_entries_async_finish (storage_manager, res, &error);
+
+	if (*search_dialog_weak_pointer == NULL) {
+		/* The dialogue's been finalised already, so we'd better just return */
+		g_clear_error (&error);
+		return;
+	}
+
+	g_return_if_fail (ALMANAH_IS_SEARCH_DIALOG (*search_dialog_weak_pointer));
+
+	search_dialog = ALMANAH_SEARCH_DIALOG (*search_dialog_weak_pointer);
+	priv = search_dialog->priv;
+
+	/* Return the search result count to the user */
+	gtk_spinner_stop (priv->sd_search_spinner);
+	gtk_widget_hide (GTK_WIDGET (priv->sd_search_spinner));
+	gtk_widget_set_sensitive (priv->sd_cancel_button, FALSE);
+	gtk_widget_set_sensitive (priv->sd_search_button, TRUE);
+
+	if (error != NULL && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) == TRUE) {
+		gtk_label_set_text (priv->sd_results_label, _("Search canceled."));
+	} else if (error != NULL) {
+		/* Translators: This is an error message wrapper for when searches encounter an error. The placeholder is for an error message. */
+		gchar *error_message = g_strdup_printf (_("Error: %s"), error->message);
+		gtk_label_set_text (priv->sd_results_label, error_message);
+		g_free (error_message);
+	} else {
+		/* Success! */
+		gchar *results_string = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "Found %d entry:", "Found %d entries:", counter), counter);
+		gtk_label_set_text (priv->sd_results_label, results_string);
+		g_free (results_string);
+	}
+
+	g_clear_error (&error);
+
+	/* Tidy up */
+	g_object_remove_weak_pointer (G_OBJECT (search_dialog), (gpointer*) search_dialog_weak_pointer);
+	g_slice_free (AlmanahSearchDialog*, search_dialog_weak_pointer);
+
+	g_object_unref (priv->sd_cancellable);
+	search_dialog->priv->sd_cancellable = NULL;
+}
+
+void
+sd_cancel_button_clicked_cb (GtkButton *self, AlmanahSearchDialog *search_dialog)
+{
+	AlmanahSearchDialogPrivate *priv = search_dialog->priv;
+
+	if (priv->sd_cancellable != NULL) {
+		g_cancellable_cancel (priv->sd_cancellable);
+	}
+}
+
 void
 sd_search_button_clicked_cb (GtkButton *self, AlmanahSearchDialog *search_dialog)
 {
 	AlmanahApplication *application;
 	AlmanahStorageManager *storage_manager;
-	AlmanahEntry *entry;
-	AlmanahStorageManagerIter iter;
 	AlmanahSearchDialogPrivate *priv = search_dialog->priv;
-	const gchar *search_string = gtk_entry_get_text (priv->sd_search_entry);
+	const gchar *search_string;
+	AlmanahSearchDialog **search_dialog_weak_pointer;
+
+	if (priv->sd_cancellable != NULL) {
+		// Already searching
+		g_assert_not_reached ();
+		return;
+	}
 
 	/* Clear the results store of previous search results first */
-	gtk_list_store_clear (search_dialog->priv->sd_results_store);
+	gtk_list_store_clear (priv->sd_results_store);
+
+	priv->sd_cancellable = g_cancellable_new ();
+
+	search_string = gtk_entry_get_text (priv->sd_search_entry);
+
+	/* Change UI to show the status */
+	gtk_widget_show (GTK_WIDGET (priv->sd_results_alignment));
+	gtk_label_set_text (priv->sd_results_label, _("Searchingâ"));
+	gtk_widget_show (GTK_WIDGET (priv->sd_search_spinner));
+	gtk_spinner_start (priv->sd_search_spinner);
 
 	/* Grab the storage manager */
 	application = ALMANAH_APPLICATION (gtk_window_get_application (GTK_WINDOW (search_dialog)));
 	storage_manager = almanah_application_dup_storage_manager (application);
 
-	/* Search over all entries */
-	almanah_storage_manager_iter_init (&iter);
-	while ((entry = almanah_storage_manager_search_entries (storage_manager, search_string, &iter)) != NULL) {
-		GDate date;
-		gchar formatted_date[100];
-		GtkTreeIter tree_iter;
-
-		almanah_entry_get_date (entry, &date);
+	/* Launch the search */
+	search_dialog_weak_pointer = g_slice_new (AlmanahSearchDialog*);
+	*search_dialog_weak_pointer = search_dialog;
+	g_object_add_weak_pointer (G_OBJECT (search_dialog), (gpointer*) search_dialog_weak_pointer);
 
-		/* Translators: This is a strftime()-format string for the dates displayed in search results. */
-		g_date_strftime (formatted_date, sizeof (formatted_date), _("%A, %e %B %Y"), &date);
+	almanah_storage_manager_search_entries_async (storage_manager, search_string, priv->sd_cancellable,
+	                                              (AlmanahStorageManagerSearchCallback) sd_search_progress_cb,
+	                                              (gpointer) search_dialog_weak_pointer, NULL,
+	                                              (GAsyncReadyCallback) sd_search_ready_cb, (gpointer) search_dialog_weak_pointer);
 
-		gtk_list_store_append (priv->sd_results_store, &tree_iter);
-		gtk_list_store_set (priv->sd_results_store, &tree_iter,
-		                    0, g_date_get_day (&date),
-		                    1, g_date_get_month (&date),
-		                    2, g_date_get_year (&date),
-		                    3, &formatted_date,
-		                    4, (almanah_entry_is_important (entry) == TRUE) ? "emblem-important" : NULL,
-		                    -1);
-
-		g_object_unref (entry);
-	}
+	/* Allow the user to cancel the search */
+	gtk_widget_set_sensitive (priv->sd_search_button, FALSE);
+	gtk_widget_set_sensitive (priv->sd_cancel_button, TRUE);
+	gtk_widget_grab_default (priv->sd_cancel_button);
 
 	g_object_unref (storage_manager);
 }
diff --git a/src/storage-manager.c b/src/storage-manager.c
index 5cb3407..5093670 100644
--- a/src/storage-manager.c
+++ b/src/storage-manager.c
@@ -992,6 +992,168 @@ almanah_storage_manager_search_entries (AlmanahStorageManager *self, const gchar
 	g_assert_not_reached ();
 }
 
+typedef struct {
+	gchar *search_string;
+	AlmanahStorageManagerSearchCallback progress_callback;
+	gpointer progress_user_data;
+	GDestroyNotify progress_user_data_destroy;
+	guint count;
+} SearchAsyncData;
+
+static void
+search_async_data_free (SearchAsyncData *data)
+{
+	g_free (data->search_string);
+
+	g_slice_free (SearchAsyncData, data);
+}
+
+typedef struct {
+	AlmanahStorageManagerSearchCallback callback;
+	AlmanahStorageManager *storage_manager;
+	AlmanahEntry *entry;
+	gpointer user_data;
+} ProgressCallbackData;
+
+static void
+progress_callback_data_free (ProgressCallbackData *data)
+{
+	g_object_unref (data->entry);
+	g_object_unref (data->storage_manager);
+
+	g_slice_free (ProgressCallbackData, data);
+}
+
+static gboolean
+search_entry_async_progress_cb (ProgressCallbackData *data)
+{
+	data->callback (data->storage_manager, data->entry, data->user_data);
+
+	return FALSE;
+}
+
+static void
+search_entries_async_thread (GSimpleAsyncResult *result, AlmanahStorageManager *storage_manager, GCancellable *cancellable)
+{
+	AlmanahStorageManagerIter iter;
+	AlmanahEntry *entry;
+	SearchAsyncData *search_data;
+	ProgressCallbackData *progress_data;
+	GError *error = NULL;
+
+	search_data = g_simple_async_result_get_op_res_gpointer (result);
+
+	almanah_storage_manager_iter_init (&iter);
+	while ((entry = almanah_storage_manager_search_entries (storage_manager, search_data->search_string, &iter)) != NULL) {
+		/* Don't do any unnecessary work */
+		if (cancellable != NULL && g_cancellable_set_error_if_cancelled (cancellable, &error)) {
+			g_simple_async_result_set_from_error (result, error);
+			g_error_free (error);
+			return;
+		}
+
+		search_data->count++;
+
+		/* Queue a progress callback for the result */
+		progress_data = g_slice_new (ProgressCallbackData);
+		progress_data->callback = search_data->progress_callback;
+		progress_data->storage_manager = g_object_ref (storage_manager);
+		progress_data->entry = g_object_ref (entry);
+		progress_data->user_data = search_data->progress_user_data;
+
+		g_idle_add_full (G_PRIORITY_HIGH_IDLE, (GSourceFunc) search_entry_async_progress_cb,
+		                 progress_data, (GDestroyNotify) progress_callback_data_free);
+	}
+}
+
+/**
+ * almanah_storage_manager_search_entries_async_finish:
+ * @self: an #AlmanahStorageManager
+ * @result: a #GSimpleAsyncResult
+ * @error: a #GError or %NULL
+ *
+ * Finish an asynchronous search started with almanah_storage_manager_search_entries_async().
+ *
+ * Return value: the number of entries which matched the search string, or <code class="literal">-1</code> on error
+ */
+gint
+almanah_storage_manager_search_entries_async_finish (AlmanahStorageManager *self, GAsyncResult *result, GError **error)
+{
+	SearchAsyncData *search_data = NULL;
+	gint retval = -1;
+
+	g_return_val_if_fail (ALMANAH_IS_STORAGE_MANAGER (self), -1);
+	g_return_val_if_fail (G_IS_ASYNC_RESULT (result), -1);
+	g_return_val_if_fail (error == NULL || *error == NULL, -1);
+
+	if (g_simple_async_result_is_valid (result, G_OBJECT (self), almanah_storage_manager_search_entries_async) == FALSE) {
+		return -1;
+	}
+
+	/* Check for errors */
+	search_data = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
+
+	/* Extract the number of results */
+	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error) == FALSE) {
+		retval = search_data->count;
+	}
+
+	/* Notify of destruction of the user data. We have to do this here so that we can guarantee that all of the progress callbacks have
+	 * been executed. */
+	if (search_data != NULL && search_data->progress_user_data_destroy != NULL) {
+		search_data->progress_user_data_destroy (search_data->progress_user_data);
+	}
+
+	return retval;
+}
+
+/**
+ * almanah_storage_manager_search_entries_async:
+ * @self: an #AlmanahStorageManager
+ * @search_string: the string of search terms being queried against
+ * @cancellable: (allow-none): a #GCancellable, or %NULL
+ * @progress_callback: (scope notified) (allow-none) (closure progress_user_data): a function to call for each result as it's found, or %NULL
+ * @progress_user_data: (closure): data to pass to @progress_callback
+ * @progress_user_data_destroy: (allow-none): a function to destroy the @progress_user_data when it will not be used any more, or %NULL
+ * @callback: a #GAsyncReadyCallback to call once the search is complete
+ * @user_data: the data to pass to @callback
+ *
+ * Launch an asynchronous search for @search_string in the content in entries in the database.
+ *
+ * When the @search_string was found in an entry, @progess_callback will be called with the #AlmanahEntry which was found.
+ *
+ * When the search finishes, @callback will be called, then you can call almanah_storage_manager_search_entries_async_finish() to get the number of
+ * entries found in total by the operation.
+ */
+void
+almanah_storage_manager_search_entries_async (AlmanahStorageManager *self, const gchar *search_string, GCancellable *cancellable,
+                                              AlmanahStorageManagerSearchCallback progress_callback, gpointer progress_user_data,
+                                              GDestroyNotify progress_user_data_destroy,
+                                              GAsyncReadyCallback callback, gpointer user_data)
+{
+	GSimpleAsyncResult *result;
+	SearchAsyncData *search_data;
+
+	g_return_if_fail (ALMANAH_IS_STORAGE_MANAGER (self));
+	g_return_if_fail (search_string != NULL);
+	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+	g_return_if_fail (callback != NULL);
+
+	result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, almanah_storage_manager_search_entries_async);
+
+	search_data = g_slice_new (SearchAsyncData);
+	search_data->search_string = g_strdup (search_string);
+	search_data->progress_callback = progress_callback;
+	search_data->progress_user_data = progress_user_data;
+	search_data->progress_user_data_destroy = progress_user_data_destroy;
+	search_data->count = 0;
+
+	g_simple_async_result_set_op_res_gpointer (result, search_data, (GDestroyNotify) search_async_data_free);
+	g_simple_async_result_run_in_thread (result, (GSimpleAsyncThreadFunc) search_entries_async_thread, G_PRIORITY_DEFAULT, cancellable);
+
+	g_object_unref (result);
+}
+
 /**
  * almanah_storage_manager_get_entries:
  * @self: an #AlmanahStorageManager
diff --git a/src/storage-manager.h b/src/storage-manager.h
index 7032e9d..74e956a 100644
--- a/src/storage-manager.h
+++ b/src/storage-manager.h
@@ -64,6 +64,8 @@ typedef struct {
 	gpointer user_data; /* to be used by #AlmanahStorageManager functions which need to associate data with a statement */
 } AlmanahStorageManagerIter;
 
+typedef void (*AlmanahStorageManagerSearchCallback) (AlmanahStorageManager *storage_manager, AlmanahEntry *entry, gpointer user_data);
+
 GType almanah_storage_manager_get_type (void);
 GQuark almanah_storage_manager_error_quark (void);
 AlmanahStorageManager *almanah_storage_manager_new (const gchar *filename, const gchar *encryption_key) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
@@ -80,6 +82,11 @@ gboolean almanah_storage_manager_set_entry (AlmanahStorageManager *self, Almanah
 void almanah_storage_manager_iter_init (AlmanahStorageManagerIter *iter);
 AlmanahEntry *almanah_storage_manager_search_entries (AlmanahStorageManager *self, const gchar *search_string,
                                                       AlmanahStorageManagerIter *iter) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
+void almanah_storage_manager_search_entries_async (AlmanahStorageManager *self, const gchar *search_string, GCancellable *cancellable,
+                                                   AlmanahStorageManagerSearchCallback progress_callback, gpointer progress_user_data,
+                                                   GDestroyNotify progress_user_data_destroy,
+                                                   GAsyncReadyCallback callback_ready, gpointer user_data);
+gint almanah_storage_manager_search_entries_async_finish (AlmanahStorageManager *self, GAsyncResult *result, GError **error);
 AlmanahEntry *almanah_storage_manager_get_entries (AlmanahStorageManager *self,
                                                    AlmanahStorageManagerIter *iter) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]