[almanah] Add progress reporting to import/export



commit 17e418195a79541755dc808574deb7924b409cd8
Author: Philip Withnall <philip tecnocode co uk>
Date:   Sun May 16 18:30:01 2010 +0100

    Add progress reporting to import/export
    
    Add a progress bar to the import/export dialogue, reporting the operation's
    activity. This makes the lifecycle of the import/export dialogue a lot more
    complex.

 data/almanah.ui            |   14 +++++-
 src/export-operation.c     |   99 ++++++++++++++++++++++++++++++++++------
 src/export-operation.h     |    8 +++-
 src/import-export-dialog.c |  108 +++++++++++++++++++++++++++++++-------------
 src/import-operation.c     |   69 ++++++++++++++++++++--------
 src/import-operation.h     |    2 +-
 src/main-window.c          |   18 ++++---
 src/main.c                 |    4 --
 src/main.h                 |    2 -
 9 files changed, 239 insertions(+), 85 deletions(-)
---
diff --git a/data/almanah.ui b/data/almanah.ui
index a58af93..c190bbf 100644
--- a/data/almanah.ui
+++ b/data/almanah.ui
@@ -941,7 +941,6 @@
 			<column type="gchararray"/><!-- Name -->
 			<column type="gchararray"/><!-- Description -->
 			<column type="guint"/><!-- Action -->
-			<column type="gchararray"/><!-- Title -->
 		</columns>
 	</object>
 
@@ -993,7 +992,7 @@
 						</child>
 						<child>
 							<object class="GtkAlignment" id="almanah_ied_alignment">
-								<property name="top-padding">12</property>
+								<property name="bottom-padding">12</property>
 								<child>
 									<object class="GtkLabel" id="almanah_ied_description_label">
 										<property name="wrap">True</property>
@@ -1007,10 +1006,19 @@
 							</packing>
 						</child>
 						<child>
-							<object class="GtkFileChooserButton" id="almanah_ied_file_chooser">
+							<object class="GtkFileChooserWidget" id="almanah_ied_file_chooser">
 								<signal name="selection-changed" handler="ied_file_chooser_selection_changed_cb"/>
 								<signal name="file-activated" handler="ied_file_chooser_file_activated_cb"/>
 								<property name="action">GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER</property>
+								<property name="do-overwrite-confirmation">True</property>
+							</object>
+						</child>
+						<child>
+							<object class="GtkAlignment" id="almanah_ied_alignment2">
+								<property name="top-padding">12</property>
+								<child>
+									<object class="GtkProgressBar" id="almanah_ied_progress_bar"/>
+								</child>
 							</object>
 							<packing>
 								<property name="expand">False</property>
diff --git a/src/export-operation.c b/src/export-operation.c
index 032f55f..47ab893 100644
--- a/src/export-operation.c
+++ b/src/export-operation.c
@@ -27,30 +27,30 @@
 #include "storage-manager.h"
 #include "main.h"
 
-typedef gboolean (*ExportFunc) (AlmanahExportOperation *self, GFile *destination, GCancellable *cancellable, GError **error);
+typedef gboolean (*ExportFunc) (AlmanahExportOperation *self, GFile *destination, AlmanahExportProgressCallback progress_callback,
+                                gpointer progress_user_data, GCancellable *cancellable, GError **error);
 
 typedef struct {
 	const gchar *name; /* translatable */
 	const gchar *description; /* translatable */
 	GtkFileChooserAction action;
-	const gchar *file_chooser_title; /* translatable */
 	ExportFunc export_func;
 } ExportModeDetails;
 
-static gboolean export_text_files (AlmanahExportOperation *self, GFile *destination, GCancellable *cancellable, GError **error);
-static gboolean export_database (AlmanahExportOperation *self, GFile *destination, GCancellable *cancellable, GError **error);
+static gboolean export_text_files (AlmanahExportOperation *self, GFile *destination, AlmanahExportProgressCallback progress_callback,
+                                   gpointer progress_user_data, GCancellable *cancellable, GError **error);
+static gboolean export_database (AlmanahExportOperation *self, GFile *destination, AlmanahExportProgressCallback progress_callback,
+                                 gpointer progress_user_data, GCancellable *cancellable, GError **error);
 
 static const ExportModeDetails export_modes[] = {
 	{ N_("Text Files"),
 	  N_("Select a _folder to export the entries to as text files, one per entry, with names in the format 'yyyy-mm-dd', and no extension. "
 	     "All entries will be exported, unencrypted in plain text format."),
 	  GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
-	  N_("Select a Folder"),
 	  export_text_files },
 	{ N_("Database"),
 	  N_("Select a _filename for a complete copy of the unencrypted Almanah Diary database to be given."),
-	  GTK_FILE_CHOOSER_ACTION_SAVE, /* TODO: Need to change the file chooser for this */
-	  N_("Select a File"),
+	  GTK_FILE_CHOOSER_ACTION_SAVE,
 	  export_database }
 };
 
@@ -104,8 +104,51 @@ almanah_export_operation_new (AlmanahExportOperationType type_id, GFile *destina
 	return export_operation;
 }
 
+typedef struct {
+	AlmanahExportProgressCallback callback;
+	gpointer user_data;
+	GDate *date;
+} ProgressData;
+
 static gboolean
-export_text_files (AlmanahExportOperation *self, GFile *destination, GCancellable *cancellable, GError **error)
+progress_idle_callback_cb (ProgressData *data)
+{
+	g_assert (data->callback != NULL);
+	data->callback (data->date, data->user_data);
+
+	/* Free the data */
+	g_date_free (data->date);
+	g_slice_free (ProgressData, data);
+
+	return FALSE;
+}
+
+static void
+progress_idle_callback (AlmanahExportProgressCallback callback, gpointer user_data, const GDate *date)
+{
+	GSource *source;
+	ProgressData *data;
+
+	data = g_slice_new (ProgressData);
+	data->callback = callback;
+	data->user_data = user_data;
+	data->date = g_date_new_dmy (g_date_get_day (date), g_date_get_month (date), g_date_get_year (date));
+
+	/* We can't just use g_idle_add() here, since GSimpleAsyncResult uses default priority, so the finished callback will skip any outstanding
+	 * progress callbacks in the main loop's priority queue, causing Bad Things to happen. We need to guarantee that no more progress callbacks
+	 * will occur after the finished callback has been called; this is one hacky way of achieving that. */
+	source = g_idle_source_new ();
+	g_source_set_priority (source, G_PRIORITY_DEFAULT);
+
+	g_source_set_callback (source, (GSourceFunc) progress_idle_callback_cb, data, NULL);
+	g_source_attach (source, NULL);
+
+	g_source_unref (source);
+}
+
+static gboolean
+export_text_files (AlmanahExportOperation *self, GFile *destination, AlmanahExportProgressCallback progress_callback, gpointer progress_user_data,
+                   GCancellable *cancellable, GError **error)
 {
 	AlmanahStorageManagerIter iter;
 	AlmanahEntry *entry;
@@ -152,8 +195,15 @@ export_text_files (AlmanahExportOperation *self, GFile *destination, GCancellabl
 			break;
 		}
 
+		/* Progress callback */
+		progress_idle_callback (progress_callback, progress_user_data, &date);
+
 		g_object_unref (file);
 		g_free (content);
+
+		/* Check for cancellation */
+		if (cancellable != NULL && g_cancellable_set_error_if_cancelled (cancellable, &child_error) == TRUE)
+			break;
 	}
 
 	/* Check if the loop was broken due to an error */
@@ -171,11 +221,14 @@ finish:
 }
 
 static gboolean
-export_database (AlmanahExportOperation *self, GFile *destination, GCancellable *cancellable, GError **error)
+export_database (AlmanahExportOperation *self, GFile *destination, AlmanahExportProgressCallback progress_callback, gpointer progress_user_data,
+                 GCancellable *cancellable, GError **error)
 {
 	GFile *source;
 	gboolean success;
 
+	/* We ignore the progress callbacks, since this is a fairly fast operation, and it exports all the entries at once. */
+
 	/* Get the input file (current unencrypted database) */
 	source = g_file_new_for_path (almanah_storage_manager_get_filename (almanah->storage_manager, TRUE));
 
@@ -187,10 +240,22 @@ export_database (AlmanahExportOperation *self, GFile *destination, GCancellable
 	return success;
 }
 
+typedef struct {
+	AlmanahExportProgressCallback progress_callback;
+	gpointer progress_user_data;
+} ExportData;
+
+static void
+export_data_free (ExportData *data)
+{
+	g_slice_free (ExportData, data);
+}
+
 static void
 export_thread (GSimpleAsyncResult *result, AlmanahExportOperation *operation, GCancellable *cancellable)
 {
 	GError *error = NULL;
+	ExportData *data = g_simple_async_result_get_op_res_gpointer (result);
 
 	/* Check to see if the operation's been cancelled already */
 	if (g_cancellable_set_error_if_cancelled (cancellable, &error) == TRUE) {
@@ -200,21 +265,29 @@ export_thread (GSimpleAsyncResult *result, AlmanahExportOperation *operation, GC
 	}
 
 	/* Export and return */
-	if (export_modes[operation->priv->current_mode].export_func (operation, operation->priv->destination, cancellable, &error) == FALSE) {
+	if (export_modes[operation->priv->current_mode].export_func (operation, operation->priv->destination, data->progress_callback,
+	    data->progress_user_data, cancellable, &error) == FALSE) {
 		g_simple_async_result_set_from_error (result, error);
 		g_error_free (error);
 	}
 }
 
 void
-almanah_export_operation_run (AlmanahExportOperation *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
+almanah_export_operation_run (AlmanahExportOperation *self, GCancellable *cancellable, AlmanahExportProgressCallback progress_callback,
+                              gpointer progress_user_data,GAsyncReadyCallback callback, gpointer user_data)
 {
 	GSimpleAsyncResult *result;
+	ExportData *data;
 
 	g_return_if_fail (ALMANAH_IS_EXPORT_OPERATION (self));
 	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
 
+	data = g_slice_new (ExportData);
+	data->progress_callback = progress_callback;
+	data->progress_user_data = progress_user_data;
+
 	result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, almanah_export_operation_run);
+	g_simple_async_result_set_op_res_gpointer (result, data, (GDestroyNotify) export_data_free);
 	g_simple_async_result_run_in_thread (result, (GSimpleAsyncThreadFunc) export_thread, G_PRIORITY_DEFAULT, cancellable);
 	g_object_unref (result);
 }
@@ -237,8 +310,7 @@ almanah_export_operation_finish (AlmanahExportOperation *self, GAsyncResult *asy
 }
 
 void
-almanah_export_operation_populate_model (GtkListStore *store, guint type_id_column, guint name_column, guint description_column, guint action_column,
-                                         guint file_chooser_title_column)
+almanah_export_operation_populate_model (GtkListStore *store, guint type_id_column, guint name_column, guint description_column, guint action_column)
 {
 	guint i;
 
@@ -251,7 +323,6 @@ almanah_export_operation_populate_model (GtkListStore *store, guint type_id_colu
 		                    name_column, _(export_modes[i].name),
 		                    description_column, _(export_modes[i].description),
 		                    action_column, export_modes[i].action,
-		                    file_chooser_title_column, _(export_modes[i].file_chooser_title),
 		                    -1);
 	}
 }
diff --git a/src/export-operation.h b/src/export-operation.h
index f0990c2..7292519 100644
--- a/src/export-operation.h
+++ b/src/export-operation.h
@@ -27,6 +27,8 @@ G_BEGIN_DECLS
 
 typedef guint AlmanahExportOperationType;
 
+typedef void (*AlmanahExportProgressCallback) (const GDate *date, gpointer user_data);
+
 #define ALMANAH_TYPE_EXPORT_OPERATION		(almanah_export_operation_get_type ())
 #define ALMANAH_EXPORT_OPERATION(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), ALMANAH_TYPE_EXPORT_OPERATION, AlmanahExportOperation))
 #define ALMANAH_EXPORT_OPERATION_CLASS(k)	(G_TYPE_CHECK_CLASS_CAST((k), ALMANAH_TYPE_EXPORT_OPERATION, AlmanahExportOperationClass))
@@ -49,11 +51,13 @@ GType almanah_export_operation_get_type (void) G_GNUC_CONST;
 
 AlmanahExportOperation *almanah_export_operation_new (AlmanahExportOperationType type_id, GFile *destination) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
 
-void almanah_export_operation_run (AlmanahExportOperation *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
+void almanah_export_operation_run (AlmanahExportOperation *self, GCancellable *cancellable,
+                                   AlmanahExportProgressCallback progress_callback, gpointer progress_user_data,
+                                   GAsyncReadyCallback callback, gpointer user_data);
 gboolean almanah_export_operation_finish (AlmanahExportOperation *self, GAsyncResult *async_result, GError **error);
 
 void almanah_export_operation_populate_model (GtkListStore *list_store, guint type_id_column, guint name_column, guint description_column,
-                                              guint action_column, guint file_chooser_title_column);
+                                              guint action_column);
 
 G_END_DECLS
 
diff --git a/src/import-export-dialog.c b/src/import-export-dialog.c
index 4883c55..c7e25b3 100644
--- a/src/import-export-dialog.c
+++ b/src/import-export-dialog.c
@@ -44,15 +44,21 @@ struct _AlmanahImportExportDialogPrivate {
 	GtkFileChooser *file_chooser;
 	GtkWidget *import_export_button;
 	GtkLabel *description_label;
+	GtkProgressBar *progress_bar;
+	GCancellable *cancellable; /* non-NULL iff an operation is underway */
 };
 
 G_DEFINE_TYPE (AlmanahImportExportDialog, almanah_import_export_dialog, GTK_TYPE_DIALOG)
 #define ALMANAH_IMPORT_EXPORT_DIALOG_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ALMANAH_TYPE_IMPORT_EXPORT_DIALOG,\
                                                        AlmanahImportExportDialogPrivate))
 
+static void almanah_import_export_dialog_dispose (GObject *object);
+
 static void
 almanah_import_export_dialog_class_init (AlmanahImportExportDialogClass *klass)
 {
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+	gobject_class->dispose = almanah_import_export_dialog_dispose;
 	g_type_class_add_private (klass, sizeof (AlmanahImportExportDialogPrivate));
 }
 
@@ -63,9 +69,17 @@ almanah_import_export_dialog_init (AlmanahImportExportDialog *self)
 	self->priv->current_mode = -1; /* no mode selected */
 
 	g_signal_connect (self, "response", G_CALLBACK (response_cb), self);
-	g_signal_connect (self, "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), self);
 	gtk_dialog_set_has_separator (GTK_DIALOG (self), FALSE);
-	gtk_window_set_transient_for (GTK_WINDOW (self), GTK_WINDOW (almanah->main_window));
+	gtk_window_set_default_size (GTK_WINDOW (self), 500, 400);
+}
+
+static void
+almanah_import_export_dialog_dispose (GObject *object)
+{
+	g_message ("almanah_import_export_dialog_dispose");
+
+	/* Chain up to the parent class */
+	G_OBJECT_CLASS (almanah_import_export_dialog_parent_class)->dispose (object);
 }
 
 /**
@@ -124,6 +138,7 @@ almanah_import_export_dialog_new (gboolean import)
 	priv->import_export_button = GTK_WIDGET (gtk_builder_get_object (builder, "almanah_ied_import_export_button"));
 	priv->mode_store = GTK_LIST_STORE (gtk_builder_get_object (builder, "almanah_ied_mode_store"));
 	priv->description_label = GTK_LABEL (gtk_builder_get_object (builder, "almanah_ied_description_label"));
+	priv->progress_bar = GTK_PROGRESS_BAR (gtk_builder_get_object (builder, "almanah_ied_progress_bar"));
 
 	/* Set the mode label */
 	gtk_label_set_text_with_mnemonic (GTK_LABEL (gtk_builder_get_object (builder, "almanah_ied_mode_label")),
@@ -137,9 +152,9 @@ almanah_import_export_dialog_new (gboolean import)
 
 	/* Populate the mode combo box */
 	if (import == TRUE)
-		almanah_import_operation_populate_model (priv->mode_store, 0, 1, 2, 3, 4);
+		almanah_import_operation_populate_model (priv->mode_store, 0, 1, 2, 3);
 	else
-		almanah_export_operation_populate_model (priv->mode_store, 0, 1, 2, 3, 4);
+		almanah_export_operation_populate_model (priv->mode_store, 0, 1, 2, 3);
 	gtk_combo_box_set_active (priv->mode_combo_box, 0);
 
 	g_object_unref (builder);
@@ -150,7 +165,11 @@ almanah_import_export_dialog_new (gboolean import)
 static void
 import_progress_cb (const GDate *date, AlmanahImportStatus status, const gchar *message, AlmanahImportResultsDialog *results_dialog)
 {
+	AlmanahImportExportDialog *self;
+g_message ("import_progress_cb");
+	self = ALMANAH_IMPORT_EXPORT_DIALOG (gtk_window_get_transient_for (GTK_WINDOW (results_dialog))); /* set in response_cb() */
 	almanah_import_results_dialog_add_result (results_dialog, date, status, message);
+	gtk_progress_bar_pulse (self->priv->progress_bar);
 }
 
 static void
@@ -158,21 +177,21 @@ import_cb (AlmanahImportOperation *operation, GAsyncResult *async_result, Almana
 {
 	AlmanahImportExportDialog *self;
 	GError *error = NULL;
-
+g_message ("import_cb");
 	self = ALMANAH_IMPORT_EXPORT_DIALOG (gtk_window_get_transient_for (GTK_WINDOW (results_dialog))); /* set in response_cb() */
 
 	/* Check for errors (e.g. errors opening databases or files; not errors importing individual entries once we have the content to import) */
 	if (almanah_import_operation_finish (operation, async_result, &error) == FALSE) {
-		/* Show an error */
-		GtkWidget *error_dialog = gtk_message_dialog_new (GTK_WINDOW (self), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
-		                                                  GTK_BUTTONS_OK, _("Import failed"));
-		gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (error_dialog), "%s", error->message);
-		gtk_dialog_run (GTK_DIALOG (error_dialog));
-		gtk_widget_destroy (error_dialog);
+		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) == FALSE) {
+			/* Show an error if the operation wasn't cancelled */
+			GtkWidget *error_dialog = gtk_message_dialog_new (GTK_WINDOW (self), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
+			                                                  GTK_BUTTONS_OK, _("Import failed"));
+			gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (error_dialog), "%s", error->message);
+			gtk_dialog_run (GTK_DIALOG (error_dialog));
+			gtk_widget_destroy (error_dialog);
+		}
 
 		g_error_free (error);
-
-		gtk_widget_hide (GTK_WIDGET (self));
 	} else {
 		/* Show the results dialogue */
 		gtk_widget_hide (GTK_WIDGET (self));
@@ -181,6 +200,17 @@ import_cb (AlmanahImportOperation *operation, GAsyncResult *async_result, Almana
 	}
 
 	gtk_widget_destroy (GTK_WIDGET (results_dialog));
+
+	g_object_unref (self->priv->cancellable);
+	self->priv->cancellable = NULL;
+
+	gtk_widget_destroy (GTK_WIDGET (self));
+}
+
+static void
+export_progress_cb (const GDate *date, AlmanahImportExportDialog *self)
+{
+	gtk_progress_bar_pulse (self->priv->progress_bar);
 }
 
 static void
@@ -190,16 +220,16 @@ export_cb (AlmanahExportOperation *operation, GAsyncResult *async_result, Almana
 
 	/* Check for errors (e.g. errors opening databases or files; not errors importing individual entries once we have the content to import) */
 	if (almanah_export_operation_finish (operation, async_result, &error) == FALSE) {
-		/* Show an error */
-		GtkWidget *error_dialog = gtk_message_dialog_new (GTK_WINDOW (self), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
-		                                                  GTK_BUTTONS_OK, _("Export failed"));
-		gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (error_dialog), "%s", error->message);
-		gtk_dialog_run (GTK_DIALOG (error_dialog));
-		gtk_widget_destroy (error_dialog);
+		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) == FALSE) {
+			/* Show an error if the operation wasn't cancelled */
+			GtkWidget *error_dialog = gtk_message_dialog_new (GTK_WINDOW (self), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
+				                                          GTK_BUTTONS_OK, _("Export failed"));
+			gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (error_dialog), "%s", error->message);
+			gtk_dialog_run (GTK_DIALOG (error_dialog));
+			gtk_widget_destroy (error_dialog);
+		}
 
 		g_error_free (error);
-
-		gtk_widget_hide (GTK_WIDGET (self));
 	} else {
 		/* Show a success message */
 		GtkWidget *message_dialog;
@@ -211,6 +241,11 @@ export_cb (AlmanahExportOperation *operation, GAsyncResult *async_result, Almana
 		gtk_dialog_run (GTK_DIALOG (message_dialog));
 		gtk_widget_destroy (message_dialog);
 	}
+
+	g_object_unref (self->priv->cancellable);
+	self->priv->cancellable = NULL;
+
+	gtk_widget_destroy (GTK_WIDGET (self));
 }
 
 static void
@@ -218,24 +253,37 @@ response_cb (GtkDialog *dialog, gint response_id, AlmanahImportExportDialog *sel
 {
 	AlmanahImportExportDialogPrivate *priv = self->priv;
 	GFile *file;
-
-	/* Just return if the user pressed Cancel */
+g_message ("response_cb");
+	/* If the user pressed Cancel, cancel the operation if we've started, and return otherwise */
 	if (response_id != GTK_RESPONSE_OK) {
-		gtk_widget_hide (GTK_WIDGET (self));
+		if (priv->cancellable == NULL)
+			gtk_widget_destroy (GTK_WIDGET (self));
+		else
+			g_cancellable_cancel (priv->cancellable);
 		return;
 	}
 
+	/* Disable the widgets */
+	gtk_widget_set_sensitive (self->priv->import_export_button, FALSE);
+	gtk_widget_set_sensitive (GTK_WIDGET (self->priv->file_chooser), FALSE);
+	gtk_widget_set_sensitive (GTK_WIDGET (self->priv->mode_combo_box), FALSE);
+
+	/* Get the input/output file or folder */
 	file = gtk_file_chooser_get_file (priv->file_chooser);
 	g_assert (file != NULL);
 
+	/* Set up for cancellation */
+	g_assert (priv->cancellable == NULL);
+	priv->cancellable = g_cancellable_new ();
+
 	if (priv->import == TRUE) {
 		/* Import the entries according to the selected method.*/
 		AlmanahImportOperation *operation;
-		AlmanahImportResultsDialog *results_dialog = almanah_import_results_dialog_new ();
+		AlmanahImportResultsDialog *results_dialog = almanah_import_results_dialog_new (); /* destroyed in import_cb() */
 		gtk_window_set_transient_for (GTK_WINDOW (results_dialog), GTK_WINDOW (self)); /* this is required in import_cb() */
 
 		operation = almanah_import_operation_new (priv->current_mode, file);
-		almanah_import_operation_run (operation, NULL, (AlmanahImportProgressCallback) import_progress_cb, results_dialog,
+		almanah_import_operation_run (operation, priv->cancellable, (AlmanahImportProgressCallback) import_progress_cb, results_dialog,
 		                              (GAsyncReadyCallback) import_cb, results_dialog);
 		g_object_unref (operation);
 	} else {
@@ -243,7 +291,8 @@ response_cb (GtkDialog *dialog, gint response_id, AlmanahImportExportDialog *sel
 		AlmanahExportOperation *operation;
 
 		operation = almanah_export_operation_new (priv->current_mode, file);
-		almanah_export_operation_run (operation, NULL, (GAsyncReadyCallback) export_cb, self);
+		almanah_export_operation_run (operation, priv->cancellable, (AlmanahExportProgressCallback) export_progress_cb, self,
+		                              (GAsyncReadyCallback) export_cb, self);
 		g_object_unref (operation);
 	}
 
@@ -257,7 +306,7 @@ ied_mode_combo_box_changed_cb (GtkComboBox *combo_box, AlmanahImportExportDialog
 	gint new_mode;
 	GtkTreeIter iter;
 	GtkTreeModel *model;
-	gchar *description, *title;
+	gchar *description;
 	GtkFileChooserAction action;
 
 	new_mode = gtk_combo_box_get_active (combo_box);
@@ -272,14 +321,11 @@ ied_mode_combo_box_changed_cb (GtkComboBox *combo_box, AlmanahImportExportDialog
 	gtk_tree_model_get (model, &iter,
 	                    2, &description,
 	                    3, &action,
-	                    4, &title,
 	                    -1);
 
 	gtk_file_chooser_set_action (priv->file_chooser, action);
-	gtk_file_chooser_button_set_title (GTK_FILE_CHOOSER_BUTTON (priv->file_chooser), title);
 	gtk_label_set_text_with_mnemonic (priv->description_label, description);
 
-	g_free (title);
 	g_free (description);
 }
 
diff --git a/src/import-operation.c b/src/import-operation.c
index 87d9b5a..44a04d6 100644
--- a/src/import-operation.c
+++ b/src/import-operation.c
@@ -34,7 +34,6 @@ typedef struct {
 	const gchar *name; /* translatable */
 	const gchar *description; /* translatable */
 	GtkFileChooserAction action;
-	const gchar *file_chooser_title; /* translatable */
 	ImportFunc import_func;
 } ImportModeDetails;
 
@@ -48,12 +47,10 @@ static const ImportModeDetails import_modes[] = {
 	  N_("Select a _folder containing text files, one per entry, with names in the format 'yyyy-mm-dd', and no extension. "
 	     "Any and all such files will be imported."),
 	  GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
-	  N_("Select a Folder"),
 	  import_text_files },
 	{ N_("Database"),
 	  N_("Select a database _file created by Almanah Diary to import."),
 	  GTK_FILE_CHOOSER_ACTION_OPEN,
-	  N_("Select a File"),
 	  import_database }
 };
 
@@ -120,7 +117,7 @@ progress_idle_callback_cb (ProgressData *data)
 {
 	g_assert (data->callback != NULL);
 	data->callback (data->date, data->status, data->message, data->user_data);
-
+g_message ("progress_idle_callback_cb");
 	/* Free the data */
 	g_free (data->message);
 	g_date_free (data->date);
@@ -133,14 +130,27 @@ static void
 progress_idle_callback (AlmanahImportProgressCallback callback, gpointer user_data, const GDate *date, AlmanahImportStatus status,
                         const gchar *message)
 {
-	ProgressData *data = g_slice_new (ProgressData);
+	GSource *source;
+	ProgressData *data;
+
+	data = g_slice_new (ProgressData);
 	data->callback = callback;
 	data->user_data = user_data;
 	data->date = g_date_new_dmy (g_date_get_day (date), g_date_get_month (date), g_date_get_year (date));
 	data->status = status;
 	data->message = g_strdup (message);
-	
-	g_idle_add ((GSourceFunc) progress_idle_callback_cb, data);
+g_message ("progress_idle_callback");
+
+	/* We can't just use g_idle_add() here, since GSimpleAsyncResult uses default priority, so the finished callback will skip any outstanding
+	 * progress callbacks in the main loop's priority queue, causing Bad Things to happen. We need to guarantee that no more progress callbacks
+	 * will occur after the finished callback has been called; this is one hacky way of achieving that. */
+	source = g_idle_source_new ();
+	g_source_set_priority (source, G_PRIORITY_DEFAULT);
+
+	g_source_set_callback (source, (GSourceFunc) progress_idle_callback_cb, data, NULL);
+	g_source_attach (source, NULL);
+
+	g_source_unref (source);
 }
 
 /**
@@ -170,7 +180,7 @@ set_entry (AlmanahImportOperation *self, AlmanahEntry *imported_entry, const gch
 	GtkTextIter existing_start, existing_end, imported_start, imported_end;
 	gchar *header_string;
 	GError *error = NULL;
-
+g_message ("set_entry");
 	/* Check to see if there's a conflict first */
 	almanah_entry_get_date (imported_entry, &entry_date);
 	existing_entry = almanah_storage_manager_get_entry (almanah->storage_manager, &entry_date);
@@ -197,7 +207,7 @@ set_entry (AlmanahImportOperation *self, AlmanahEntry *imported_entry, const gch
 
 	/* The two buffers have to use the same tag table for gtk_text_buffer_insert_range() to work */
 	existing_buffer = gtk_text_buffer_new (gtk_text_buffer_get_tag_table (imported_buffer));
-	if (almanah_entry_get_content (existing_entry, existing_buffer, FALSE, NULL) == FALSE) {
+	if (almanah_entry_get_content (existing_entry, existing_buffer, FALSE, &error) == FALSE) {
 		/* Deserialising the existing entry failed; use the imported entry instead */
 		almanah_storage_manager_set_entry (almanah->storage_manager, imported_entry);
 
@@ -277,7 +287,7 @@ import_text_files (AlmanahImportOperation *self, GFile *source, AlmanahImportPro
 	GFileEnumerator *enumerator;
 	GtkTextBuffer *buffer;
 	GError *child_error = NULL;
-
+g_message ("import_text_files");
 	enumerator = g_file_enumerate_children (source, "standard::name,standard::display-name,standard::is-hidden,time::modified",
 	                                        G_FILE_QUERY_INFO_NONE, NULL, error);
 	if (enumerator == NULL)
@@ -343,6 +353,10 @@ import_text_files (AlmanahImportOperation *self, GFile *source, AlmanahImportPro
 
 		g_object_unref (entry);
 		g_object_unref (file_info);
+
+		/* Check for cancellation */
+		if (cancellable != NULL && g_cancellable_set_error_if_cancelled (cancellable, &child_error) == TRUE)
+			break;
 	}
 
 	/* Check if the loop was broken due to an error */
@@ -368,14 +382,19 @@ import_database (AlmanahImportOperation *self, GFile *source, AlmanahImportProgr
 	GFileInfo *file_info;
 	gchar *path;
 	const gchar *display_name;
-	GSList *i, *definitions;
+	GSList *i, *definitions = NULL;
 	AlmanahEntry *entry;
 	AlmanahStorageManager *database;
 	AlmanahStorageManagerIter iter;
+	gboolean success = FALSE;
 
 	/* Get the display name for use with set_entry(), below */
-	file_info = g_file_query_info (source, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, G_FILE_QUERY_INFO_NONE, NULL, NULL);
+	file_info = g_file_query_info (source, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, G_FILE_QUERY_INFO_NONE, cancellable, error);
+	if (file_info == NULL)
+		return FALSE;
+
 	display_name = g_file_info_get_display_name (file_info);
+	g_object_unref (file_info);
 
 	/* Open the database */
 	path = g_file_get_path (source);
@@ -402,6 +421,10 @@ import_database (AlmanahImportOperation *self, GFile *source, AlmanahImportProgr
 		g_free (message);
 
 		g_object_unref (entry);
+
+		/* Check for cancellation */
+		if (cancellable != NULL && g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE)
+			goto finish;
 	}
 
 	/* Query for every definition */
@@ -410,14 +433,22 @@ import_database (AlmanahImportOperation *self, GFile *source, AlmanahImportProgr
 		/* Add the definition to the proper database, ignoring failure */
 		almanah_storage_manager_add_definition (almanah->storage_manager, ALMANAH_DEFINITION (i->data));
 		g_object_unref (i->data);
+
+		/* Check for cancellation */
+		if (cancellable != NULL && g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE)
+			goto finish;
 	}
+
+	/* Success! */
+	success = TRUE;
+
+finish:
 	g_slist_free (definitions);
 
 	almanah_storage_manager_disconnect (database, NULL);
 	g_object_unref (database);
-	g_object_unref (file_info);
 
-	return TRUE;
+	return success;
 }
 
 typedef struct {
@@ -436,7 +467,7 @@ import_thread (GSimpleAsyncResult *result, AlmanahImportOperation *operation, GC
 {
 	GError *error = NULL;
 	ImportData *data = g_simple_async_result_get_op_res_gpointer (result);
-
+g_message ("import_thread");
 	/* Check to see if the operation's been cancelled already */
 	if (g_cancellable_set_error_if_cancelled (cancellable, &error) == TRUE) {
 		g_simple_async_result_set_from_error (result, error);
@@ -458,7 +489,7 @@ almanah_import_operation_run (AlmanahImportOperation *self, GCancellable *cancel
 {
 	GSimpleAsyncResult *result;
 	ImportData *data;
-
+g_message ("almanah_import_operation_run");
 	g_return_if_fail (ALMANAH_IS_IMPORT_OPERATION (self));
 	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
 
@@ -476,7 +507,7 @@ gboolean
 almanah_import_operation_finish (AlmanahImportOperation *self, GAsyncResult *async_result, GError **error)
 {
 	GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (async_result);
-
+g_message ("almanah_import_operation_finish");
 	g_return_val_if_fail (ALMANAH_IS_IMPORT_OPERATION (self), FALSE);
 	g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), FALSE);
 	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
@@ -490,8 +521,7 @@ almanah_import_operation_finish (AlmanahImportOperation *self, GAsyncResult *asy
 }
 
 void
-almanah_import_operation_populate_model (GtkListStore *store, guint type_id_column, guint name_column, guint description_column, guint action_column,
-                                         guint file_chooser_title_column)
+almanah_import_operation_populate_model (GtkListStore *store, guint type_id_column, guint name_column, guint description_column, guint action_column)
 {
 	guint i;
 
@@ -504,7 +534,6 @@ almanah_import_operation_populate_model (GtkListStore *store, guint type_id_colu
 		                    name_column, _(import_modes[i].name),
 		                    description_column, _(import_modes[i].description),
 		                    action_column, import_modes[i].action,
-		                    file_chooser_title_column, _(import_modes[i].file_chooser_title),
 		                    -1);
 	}
 }
diff --git a/src/import-operation.h b/src/import-operation.h
index 72c2c80..921f5fd 100644
--- a/src/import-operation.h
+++ b/src/import-operation.h
@@ -65,7 +65,7 @@ void almanah_import_operation_run (AlmanahImportOperation *self, GCancellable *c
 gboolean almanah_import_operation_finish (AlmanahImportOperation *self, GAsyncResult *async_result, GError **error);
 
 void almanah_import_operation_populate_model (GtkListStore *list_store, guint type_id_column, guint name_column, guint description_column,
-                                              guint action_column, guint file_chooser_title_column);
+                                              guint action_column);
 
 G_END_DECLS
 
diff --git a/src/main-window.c b/src/main-window.c
index 83ed2e8..7672211 100644
--- a/src/main-window.c
+++ b/src/main-window.c
@@ -713,21 +713,23 @@ mw_delete_event_cb (GtkWindow *window, gpointer user_data)
 void
 mw_import_activate_cb (GtkAction *action, AlmanahMainWindow *main_window)
 {
-	if (almanah->import_dialog == NULL)
-		almanah->import_dialog = GTK_WIDGET (almanah_import_export_dialog_new (TRUE));
+	GtkWidget *dialog = GTK_WIDGET (almanah_import_export_dialog_new (TRUE));
+	gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (main_window));
+	gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
 
-	gtk_widget_show_all (almanah->import_dialog);
-	gtk_dialog_run (GTK_DIALOG (almanah->import_dialog));
+	/* The dialog destroys itself once done */
+	gtk_widget_show_all (dialog);
 }
 
 void
 mw_export_activate_cb (GtkAction *action, AlmanahMainWindow *main_window)
 {
-	if (almanah->export_dialog == NULL)
-		almanah->export_dialog = GTK_WIDGET (almanah_import_export_dialog_new (FALSE));
+	GtkWidget *dialog = GTK_WIDGET (almanah_import_export_dialog_new (FALSE));
+	gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (main_window));
+	gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
 
-	gtk_widget_show_all (almanah->export_dialog);
-	gtk_dialog_run (GTK_DIALOG (almanah->export_dialog));
+	/* The dialog destroys itself once done */
+	gtk_widget_show_all (dialog);
 }
 
 void
diff --git a/src/main.c b/src/main.c
index 77001fa..93200af 100644
--- a/src/main.c
+++ b/src/main.c
@@ -73,10 +73,6 @@ almanah_quit (void)
 		gtk_widget_destroy (almanah->search_dialog);
 	if (almanah->date_entry_dialog != NULL)
 		gtk_widget_destroy (almanah->date_entry_dialog);
-	if (almanah->import_dialog != NULL)
-		gtk_widget_destroy (almanah->import_dialog);
-	if (almanah->export_dialog != NULL)
-		gtk_widget_destroy (almanah->export_dialog);
 #ifdef ENABLE_ENCRYPTION
 	if (almanah->preferences_dialog != NULL)
 		gtk_widget_destroy (almanah->preferences_dialog);
diff --git a/src/main.h b/src/main.h
index 085c3df..08f9d50 100644
--- a/src/main.h
+++ b/src/main.h
@@ -46,8 +46,6 @@ typedef struct {
 	GtkWidget *search_dialog;
 	GtkWidget *date_entry_dialog;
 	GtkWidget *definition_manager_window;
-	GtkWidget *import_dialog;
-	GtkWidget *export_dialog;
 #ifdef ENABLE_ENCRYPTION
 	GtkWidget *preferences_dialog;
 #endif /* ENABLE_ENCRYPTION */



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