[PATCH 1/2] Added client side communication to the Subsurface Web Service

Lubomir I. Ivanov neolit123 at gmail.com
Thu Jan 17 17:12:28 PST 2013


From: "Lubomir I. Ivanov" <neolit123 at gmail.com>

A couple of new files webservice.c and webservice.h are added.

webservice.h exposes two methods at the moment:
- webservice_download_dialog():
this function creates the user interface for the download dialog
from the web service.
- webservice_request_user_xml()
this function is a direct call to retrieve XML for a specific
user identifier. the actual data, data length and error codes
are stored in passed pointers.

A menu entry is added in the Log menu:
"Download From Web Service"

The used backend for communication at the moment is provided
by libsoup.

Signed-off-by: Lubomir I. Ivanov <neolit123 at gmail.com>
---

fixed a couple of typos.

---
 gtk-gui.c    |   3 +
 webservice.c | 207 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 webservice.h |   2 +
 3 files changed, 212 insertions(+)
 create mode 100644 webservice.c
 create mode 100644 webservice.h

diff --git a/gtk-gui.c b/gtk-gui.c
index 304dea3..3376390 100644
--- a/gtk-gui.c
+++ b/gtk-gui.c
@@ -21,6 +21,7 @@
 #include "callbacks-gtk.h"
 #include "uemis.h"
 #include "device.h"
+#include "webservice.h"
 
 #include "libdivecomputer.h"
 
@@ -1058,6 +1059,7 @@ static GtkActionEntry menu_items[] = {
 	{ "Print",          GTK_STOCK_PRINT, N_("Print..."),  CTRLCHAR "P", NULL, G_CALLBACK(do_print) },
 	{ "ImportFile",     GTK_STOCK_GO_BACK, N_("Import XML File(s)..."), CTRLCHAR "I", NULL, G_CALLBACK(import_files) },
 	{ "DownloadLog",    GTK_STOCK_GO_DOWN, N_("Download From Dive Computer..."), CTRLCHAR "D", NULL, G_CALLBACK(download_dialog) },
+	{ "DownloadWeb",    GTK_STOCK_CONNECT, N_("Download From Web Service..."), NULL, NULL, G_CALLBACK(webservice_download_dialog) },
 	{ "AddDive",        GTK_STOCK_ADD, N_("Add Dive..."), NULL, NULL, G_CALLBACK(add_dive_cb) },
 	{ "Preferences",    GTK_STOCK_PREFERENCES, N_("Preferences..."), PREFERENCE_ACCEL, NULL, G_CALLBACK(preferences_dialog) },
 	{ "Renumber",       NULL, N_("Renumber..."), NULL, NULL, G_CALLBACK(renumber_dialog) },
@@ -1104,6 +1106,7 @@ static const gchar* ui_string = " \
 			</menu> \
 			<menu name=\"LogMenu\" action=\"LogMenuAction\"> \
 				<menuitem name=\"Download From Dive Computer\" action=\"DownloadLog\" /> \
+				<menuitem name=\"Download From Web Service\" action=\"DownloadWeb\" /> \
 				<separator name=\"Separator1\"/> \
 				<menuitem name=\"Add Dive\" action=\"AddDive\" /> \
 				<separator name=\"Separator2\"/> \
diff --git a/webservice.c b/webservice.c
new file mode 100644
index 0000000..d0c3bd8
--- /dev/null
+++ b/webservice.c
@@ -0,0 +1,207 @@
+#include <libintl.h>
+#include <glib/gi18n.h>
+#include <libsoup/soup.h>
+#include <libxml/tree.h>
+#include <libxml/parser.h>
+#include "dive.h"
+#include "display-gtk.h"
+
+enum {
+	DD_STATUS_OK,
+	DD_STATUS_ERROR_CONNECT,
+	DD_STATUS_ERROR_ID,
+	DD_STATUS_ERROR_PARSE,
+};
+
+static const gchar *download_dialog_status_text(const gint status)
+{
+	switch (status)	{
+	case DD_STATUS_ERROR_CONNECT:
+		return _("Connection Error: ");
+		break;
+	case DD_STATUS_ERROR_ID:
+		return _("Invalid user identifier!");
+		break;
+	case DD_STATUS_ERROR_PARSE:
+		return _("Cannot parse response!");
+	}
+	return _("Download Success!");
+}
+
+/* provides a state of the download dialog contents and the download xml */
+struct download_dialog_state {
+	GtkWidget *uid;
+	GtkWidget *status;
+	GtkWidget *apply;
+	gchar *xmldata;
+};
+
+/* this method uses libsoup as a backend. if there are some portability,
+ * compatibility or speed issues, libcurl is a better choice. */
+gboolean webservice_request_user_xml(const gchar *user_id,
+	gchar **data,
+	guint *len,
+	guint *status_code)
+{
+	SoupMessage *msg;
+	SoupSession *session;
+	gboolean ret = FALSE;
+	gchar url[80] = {0};
+
+	session = soup_session_async_new();
+	strcat(url, "http://api.hohndel.org/api/mydives/");
+	strcat(url, user_id);
+	strcat(url, "/xml");
+	msg = soup_message_new("GET", url);
+	soup_session_send_message(session, msg);
+	if SOUP_STATUS_IS_SUCCESSFUL(msg->status_code) {
+		*len = (guint)msg->response_body->length;
+		*data = strdup((gchar *)msg->response_body->data);
+		ret = TRUE;
+	} else {
+		*len = 0;
+		*data = NULL;
+	}
+	*status_code = msg->status_code;
+	soup_session_abort(session);
+	g_object_unref(G_OBJECT(msg));
+	g_object_unref(G_OBJECT(session));
+	return ret;
+}
+
+static void download_dialog_traverse_xml(xmlNodePtr node, gboolean *download_status)
+{
+	xmlNodePtr cur_node;
+
+	for (cur_node = node; cur_node; cur_node = cur_node->next) {
+		if (!strcmp(cur_node->name, (const gchar *)"download")) {
+			if (!strcmp(xmlNodeGetContent(cur_node), (const gchar *)"ok")) {
+				*download_status = DD_STATUS_OK;
+				return;
+			}
+		} else if (!strcmp(cur_node->name, (const gchar *)"error")) {
+			*download_status = DD_STATUS_ERROR_ID;
+		} else {
+			download_dialog_traverse_xml(cur_node->children, download_status);
+		}
+	}
+}
+
+static guint download_dialog_parse_response(gchar *xmldata, guint len)
+{
+	xmlNodePtr root;
+	xmlDocPtr doc = xmlParseMemory(xmldata, len);
+	guint status = DD_STATUS_ERROR_PARSE;
+
+	if (!doc)
+		return DD_STATUS_ERROR_PARSE;
+	root = xmlDocGetRootElement(doc);
+	if (!root) {
+		status = DD_STATUS_ERROR_PARSE;
+		goto end;
+	}
+	download_dialog_traverse_xml(root, &status);
+	end:
+		xmlFreeDoc(doc);
+		return status;
+}
+
+static void download_dialog_connect_cb(GtkWidget *w, gpointer data)
+{
+	struct download_dialog_state *state = (struct download_dialog_state *)data;
+	const gchar *uid = gtk_entry_get_text(GTK_ENTRY(state->uid));
+	guint len, status_connect, status_xml;
+	gchar *xmldata;
+	gboolean ret;
+	gchar err[128] = {0};
+
+	gtk_label_set_text(GTK_LABEL(state->status), _("Connecting..."));
+	gtk_widget_set_sensitive(state->apply, FALSE);
+	ret = webservice_request_user_xml(uid, &xmldata, &len, &status_connect);
+	if (ret) {
+		status_xml = download_dialog_parse_response(xmldata, len);
+		gtk_label_set_text(GTK_LABEL(state->status), download_dialog_status_text(status_xml));
+		if (status_xml != DD_STATUS_OK)
+			ret = FALSE;
+	} else {
+		sprintf(err, "%s %u!", download_dialog_status_text(DD_STATUS_ERROR_CONNECT), status_connect);
+		gtk_label_set_text(GTK_LABEL(state->status), err);
+	}
+	state->xmldata = xmldata;
+	gtk_widget_set_sensitive(state->apply, ret);
+}
+
+static void download_dialog_release_xml(struct download_dialog_state *state)
+{
+	if (state->xmldata)
+		free((void *)state->xmldata);
+}
+
+static void download_dialog_delete(GtkWidget *w, gpointer data)
+{
+	struct download_dialog_state *state = (struct download_dialog_state *)data;
+	download_dialog_release_xml(state);
+}
+
+void webservice_download_dialog(void)
+{
+	const guint pad = 6;
+	/* user entered value should be stored in the config */q
+	const gchar *current_uid = "41TFEC8ZMVD5DBE0JPBBU5JDDA2Y6T";
+	GtkWidget *dialog, *vbox, *status, *info, *uid;
+	GtkWidget *frame_uid, *frame_status, *download, *image, *apply;
+	struct download_dialog_state state = {NULL};
+	int result;
+
+	dialog = gtk_dialog_new_with_buttons(_("Download From Web Service"),
+		GTK_WINDOW(main_window),
+		GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+		GTK_STOCK_APPLY,
+		GTK_RESPONSE_ACCEPT,
+		GTK_STOCK_CANCEL,
+		GTK_RESPONSE_REJECT,
+		NULL);
+
+	apply = gtk_dialog_get_widget_for_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
+	gtk_widget_set_sensitive(apply, FALSE);
+
+	vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+	info = gtk_label_new(_("Enter a user identifier and press 'Download'."
+												 " Once the download is complete you can press 'Apply'"
+												 " if you wish to apply the changes."));
+	gtk_label_set_line_wrap(GTK_LABEL(info), TRUE);
+	gtk_box_pack_start(GTK_BOX(vbox), info, FALSE, TRUE, 0);
+	gtk_misc_set_padding(GTK_MISC(info), pad, pad);
+
+	frame_uid = gtk_frame_new(_("User Identifier"));
+	gtk_box_pack_start(GTK_BOX(vbox), frame_uid, FALSE, TRUE, pad);
+	uid = gtk_entry_new();
+	gtk_container_add(GTK_CONTAINER(frame_uid), uid);
+	gtk_entry_set_text(GTK_ENTRY(uid), current_uid);
+
+	download = gtk_button_new_with_label(_(" Download"));
+	image = gtk_image_new_from_stock(GTK_STOCK_CONNECT, GTK_ICON_SIZE_MENU);
+	gtk_button_set_image(GTK_BUTTON(download), image);
+	gtk_box_pack_start(GTK_BOX(vbox), download, FALSE, TRUE, pad);
+	g_signal_connect(download, "clicked", G_CALLBACK(download_dialog_connect_cb), &state);
+
+	frame_status = gtk_frame_new(_("Status"));
+	status = gtk_label_new(_("Idle"));
+	gtk_box_pack_start(GTK_BOX(vbox), frame_status, FALSE, TRUE, pad);
+	gtk_container_add(GTK_CONTAINER(frame_status), status);
+	gtk_misc_set_padding(GTK_MISC(status), pad, pad);
+
+	state.uid = uid;
+	state.status = status;
+	state.apply = apply;
+
+	gtk_widget_show_all(dialog);
+	g_signal_connect(dialog, "delete-event", G_CALLBACK(download_dialog_delete), &state);
+	result = gtk_dialog_run(GTK_DIALOG(dialog));
+	if (result == GTK_RESPONSE_ACCEPT) {
+		/* apply download */
+		g_message("\napply download should happen here: \n\n %s", state.xmldata);
+	}
+	download_dialog_release_xml(&state);
+	gtk_widget_destroy(dialog);
+}
diff --git a/webservice.h b/webservice.h
new file mode 100644
index 0000000..ea74885
--- /dev/null
+++ b/webservice.h
@@ -0,0 +1,2 @@
+extern void webservice_download_dialog(void);
+extern gboolean webservice_request_user_xml(const gchar *, gchar **, guint *, guint *);
-- 
1.7.11.msysgit.0



More information about the subsurface mailing list