From a5ee2b66e1cc9b1b7b0b7437517a30bee5b0054f Mon Sep 17 00:00:00 2001 From: "Lubomir I. Ivanov" Date: Fri, 18 Jan 2013 03:05:48 +0200 Subject: [PATCH] Added client side communication to the Subsurface Web Service 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 Signed-off-by: Dirk Hohndel --- 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 304dea344..3376390a0 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 = " \ \ \ \ + \ \ \ \ diff --git a/webservice.c b/webservice.c new file mode 100644 index 000000000..01bf76263 --- /dev/null +++ b/webservice.c @@ -0,0 +1,207 @@ +#include +#include +#include +#include +#include +#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: + _("Invalid user identifier!"); + break; + case DD_STATUS_ERROR_PARSE: + _("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 */ + 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 000000000..ea74885c1 --- /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 *);