[PATCH] Allow the user to cancel a dive computer download

Dirk Hohndel dirk at hohndel.org
Tue Nov 20 11:50:20 PST 2012


The code pretended to support this for libdivecomputer based downloads,
but it had never been hooked up when the native Uemis downloader was
implemented. When I finally decided to close that feature gap I realized
that the original code was, shall we say, "aspirational" or "completely
bogus" and therefore never worked.

So instead of just hooking up the code for the Uemis downloader I instead
implemented this correctly for the first time for both libdivecomputer and
the native Uemis downloader.

In order not to have to mess with multithreaded Gtk development I simply
opted for a helper function that fires on a 100ms timeout and have it end
the dialog without a response. This way we can run the dialog while
waiting for the download to finish, still update the progress bar and
respond in a useful manner to the user clicking cancel.

Signed-off-by: Dirk Hohndel <dirk at hohndel.org>
---

I pushed this out on master but would like to make sure that this gets
tested on Mac and Windows (and, actually, on Linux as well, just in
case). It seems fairly obvious and straight forward, but I want to make
sure it actually, say, works this time :-)

/D


 display-gtk.h      |  2 +-
 gtk-gui.c          |  5 +++--
 libdivecomputer.c  | 42 +++++++++++++++++++++++++++++++----
 libdivecomputer.h  |  1 +
 uemis-downloader.c | 65 +++++++++++++++++++++++++++++++++++++++++++++---------
 5 files changed, 97 insertions(+), 18 deletions(-)

diff --git a/display-gtk.h b/display-gtk.h
index 4f156c7..e1fd4f5 100644
--- a/display-gtk.h
+++ b/display-gtk.h
@@ -114,6 +114,6 @@ extern GtkTreeViewColumn *tree_view_column(GtkWidget *tree_view, int index, cons
 		data_func_t data_func, unsigned int flags);
 
 GError *uemis_download(const char *path, char **divenr, char **xml_buffer,
-			progressbar_t *progress, gboolean force_download);
+			progressbar_t *progress, GtkDialog *dialog, gboolean force_download);
 
 #endif
diff --git a/gtk-gui.c b/gtk-gui.c
index 3e9a35f..96dbfa3 100644
--- a/gtk-gui.c
+++ b/gtk-gui.c
@@ -1678,7 +1678,7 @@ static GError *setup_uemis_import(device_data_t *data)
 	GError *error = NULL;
 	char *buf = NULL;
 
-	error = uemis_download(data->devname, &uemis_max_dive_data, &buf, &data->progress, data->force_download);
+	error = uemis_download(data->devname, &uemis_max_dive_data, &buf, &data->progress, data->dialog, data->force_download);
 	if (buf && strlen(buf) > 1) {
 #if UEMIS_DEBUG > 3
 		fprintf(debugfile, "xml buffer \"%s\"\n\n", buf);
@@ -1742,7 +1742,7 @@ void download_dialog(GtkWidget *w, gpointer data)
 		GTK_WINDOW(main_window),
 		GTK_DIALOG_DESTROY_WITH_PARENT,
 		GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
-		GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
 		NULL);
 	g_signal_connect(dialog, "delete-event", G_CALLBACK(download_dialog_delete), NULL);
 
@@ -1819,6 +1819,7 @@ repeat:
 			while (*(--ne) == ' ' || *ne == '\t')
 				*ne = '\0';
 		devicedata.devname = ns;
+		devicedata.dialog = GTK_DIALOG(dialog);
 		devicedata.force_download = force_download;
 		force_download = FALSE; /* when retrying we don't want to restart */
 		info = import_dive_computer(&devicedata, GTK_DIALOG(dialog));
diff --git a/libdivecomputer.c b/libdivecomputer.c
index 076126d..d4ffcc1 100644
--- a/libdivecomputer.c
+++ b/libdivecomputer.c
@@ -423,21 +423,55 @@ static void *pthread_wrapper(void *_data)
 	return (void *)err_string;
 }
 
+/* this simply ends the dialog without a response and asks not to be fired again
+ * as we set this function up in every loop while uemis_download is waiting for
+ * the download to finish */
+static gboolean timeout_func(gpointer _data)
+{
+	GtkDialog *dialog = _data;
+	if (!import_thread_cancelled)
+		gtk_dialog_response(dialog, GTK_RESPONSE_NONE);
+	return FALSE;
+}
+
 GError *do_import(device_data_t *data)
 {
 	pthread_t pthread;
 	void *retval;
+	GtkDialog *dialog = data->dialog;
 
 	/* I'm sure there is some better interface for waiting on a thread in a UI main loop */
 	import_thread_done = 0;
 	progress_bar_text = "";
 	progress_bar_fraction = 0.0;
 	pthread_create(&pthread, NULL, pthread_wrapper, data);
+	/* loop here until the import is done or was cancelled by the user;
+	 * in order to get control back from gtk we register a timeout function
+	 * that ends the dialog with no response every 100ms; we then update the
+	 * progressbar and setup the timeout again - unless of course the user
+	 * pressed cancel, in which case we just wait for the download thread
+	 * to react to that and exit */
 	while (!import_thread_done) {
-		import_thread_cancelled = process_ui_events();
-		update_progressbar(&data->progress, progress_bar_fraction);
-		update_progressbar_text(&data->progress, progress_bar_text);
-		usleep(100000);
+		if (!import_thread_cancelled) {
+			int result;
+			g_timeout_add(100, timeout_func, dialog);
+			update_progressbar(&data->progress, progress_bar_fraction);
+			update_progressbar_text(&data->progress, progress_bar_text);
+			result = gtk_dialog_run(dialog);
+			switch (result) {
+			case GTK_RESPONSE_CANCEL:
+				import_thread_cancelled = TRUE;
+				progress_bar_text = "Cancelled...";
+				break;
+			default:
+				/* nothing */
+				break;
+			}
+		} else {
+			update_progressbar(&data->progress, progress_bar_fraction);
+			update_progressbar_text(&data->progress, progress_bar_text);
+			usleep(100000);
+		}
 	}
 	if (pthread_join(pthread, &retval) < 0)
 		retval = _("Odd pthread error return");
diff --git a/libdivecomputer.h b/libdivecomputer.h
index 7497abf..2121c27 100644
--- a/libdivecomputer.h
+++ b/libdivecomputer.h
@@ -18,6 +18,7 @@ typedef struct device_data_t {
 	progressbar_t progress;
 	int preexisting;
 	gboolean force_download;
+	GtkDialog *dialog;
 } device_data_t;
 
 extern GError *do_import(device_data_t *data);
diff --git a/uemis-downloader.c b/uemis-downloader.c
index fbab3dc..ac45940 100644
--- a/uemis-downloader.c
+++ b/uemis-downloader.c
@@ -510,6 +510,8 @@ static gboolean uemis_get_answer(const char *path, char *request, int n_param_in
 	mbuf = NULL;
 	mbuf_size = 0;
 	while (searching || assembling_mbuf) {
+		if (import_thread_cancelled)
+			return FALSE;
 		progress_bar_fraction = filenr / 4000.0;
 		snprintf(fl, 13, "ANS%d.TXT", filenr - 1);
 		ans_path = g_build_filename(path, "ANS", fl, NULL);
@@ -519,7 +521,7 @@ static gboolean uemis_get_answer(const char *path, char *request, int n_param_in
 #if UEMIS_DEBUG > 3
 		tmp[100]='\0';
 		fprintf(debugfile, "::t %s \"%s\"\n", ans_path, tmp);
-#elsif UEMIS_DEBUG > 1
+#elif UEMIS_DEBUG > 1
 		char pbuf[4];
 		pbuf[0] = tmp[0];
 		pbuf[1] = tmp[1];
@@ -816,6 +818,9 @@ static char *do_uemis_download(struct argument_block *args)
 	uemis_info("Start download");
 	if (! uemis_get_answer(mountpath, "processSync", 0, 2, &result))
 		goto bail;
+	/* before starting the long download, check if user pressed cancel */
+	if (import_thread_cancelled)
+		goto bail;
 	param_buff[1] = "notempty";
 	/* if we have an empty divelist then the user will almost
 	 * certainly want to start downloading from the first dive on
@@ -841,6 +846,9 @@ static char *do_uemis_download(struct argument_block *args)
 		/* if we got an error, deal with it */
 		if (!success)
 			break;
+		/* if the user clicked cancel, exit gracefully but ignore everything read */
+		if (import_thread_cancelled)
+			goto bail;
 		/* also, if we got nothing back, we should stop trying */
 		if (!param_buff[3])
 			break;
@@ -871,22 +879,24 @@ static char *do_uemis_download(struct argument_block *args)
 			buffer_insert(xml_buffer, &xml_buffer_size, next_detail);
 			free(next_detail);
 		}
-		if (!success)
+		if (!success || import_thread_cancelled)
 			break;
 	}
-	if (! uemis_get_answer(mountpath, "terminateSync", 0, 3, &result))
-		goto bail;
+bail:
+	(void) uemis_get_answer(mountpath, "terminateSync", 0, 3, &result);
 	if (! strcmp(param_buff[0], "error")) {
 		if (! strcmp(param_buff[2],"Out of Memory"))
 			result = _(ERR_FS_FULL);
 		else
 			result = param_buff[2];
 	}
-	buffer_add(xml_buffer, &xml_buffer_size, "</list></dives>");
+	if (import_thread_cancelled)
+		xml_buffer[0] = 0;
+	else
+		buffer_add(xml_buffer, &xml_buffer_size, "</list></dives>");
 #if UEMIS_DEBUG > 5
 	fprintf(debugfile, "XML buffer \"%s\"", *xml_buffer);
 #endif
-bail:
 	free(deviceid);
 	return result;
 }
@@ -899,8 +909,19 @@ static void *pthread_wrapper(void *_data)
 	return (void *)err_string;
 }
 
+/* this simply ends the dialog without a response and asks not to be fired again
+ * as we set this function up in every loop while uemis_download is waiting for
+ * the download to finish */
+static gboolean timeout_func(gpointer _data)
+{
+	GtkDialog *dialog = _data;
+	if (!import_thread_cancelled)
+		gtk_dialog_response(dialog, GTK_RESPONSE_NONE);
+	return FALSE;
+}
+
 GError *uemis_download(const char *mountpath, char **max_dive_data, char **xml_buffer, progressbar_t *progress,
-			gboolean force_download)
+			GtkDialog *dialog, gboolean force_download)
 {
 	pthread_t pthread;
 	void *retval;
@@ -911,11 +932,33 @@ GError *uemis_download(const char *mountpath, char **max_dive_data, char **xml_b
 	progress_bar_text = "";
 	progress_bar_fraction = 0.0;
 	pthread_create(&pthread, NULL, pthread_wrapper, &args);
+	/* loop here until the import is done or was cancelled by the user;
+	 * in order to get control back from gtk we register a timeout function
+	 * that ends the dialog with no response every 100ms; we then update the
+	 * progressbar and setup the timeout again - unless of course the user
+	 * pressed cancel, in which case we just wait for the download thread
+	 * to react to that and exit */
 	while (!import_thread_done) {
-		import_thread_cancelled = process_ui_events();
-		update_progressbar(args.progress, progress_bar_fraction);
-		update_progressbar_text(args.progress, progress_bar_text);
-		usleep(100000);
+		if (!import_thread_cancelled) {
+			int result;
+			g_timeout_add(100, timeout_func, dialog);
+			update_progressbar(args.progress, progress_bar_fraction);
+			update_progressbar_text(args.progress, progress_bar_text);
+			result = gtk_dialog_run(dialog);
+			switch (result) {
+			case GTK_RESPONSE_CANCEL:
+				import_thread_cancelled = TRUE;
+				progress_bar_text = "Cancelled...";
+				break;
+			default:
+				/* nothing */
+				break;
+			}
+		} else {
+			update_progressbar(args.progress, progress_bar_fraction);
+			update_progressbar_text(args.progress, progress_bar_text);
+			usleep(100000);
+		}
 	}
 	if (pthread_join(pthread, &retval) < 0)
 		return error("Pthread return with error");
-- 
1.8.0.rc0.18.gf84667d



More information about the subsurface mailing list