summaryrefslogblamecommitdiffstats
path: root/webservice.c
blob: f4f8baeb4c6e153243ae85554de97c1510da621f (plain) (tree)
1
2
3
4
5
6
7
8
9




                          
                     
                        
                 
 
                                     
                                                 
 










                                                                  
                                
                                                     
                                   
                                                   


                                      
                                                                             



                              
                          










                                                                        
                             
                                           
                                                                   
                                                             
                                           
                                                                                














                                                                  
                                                                                 
                            
                                                                    
                                                                                                     
                                                        
                                                                                                
                                                              
                               















                                                                      
                                                                      










                                                                                   
                             








                                                                                                      
                                                                                                                           

                                                                  
                                 







                                                                            






                                                 
                                                                                   
                                                                                   





                                                                     
                                                 
                                                                                                                 
                                                    
              
                                                 
      
                 






                                                                                                 
 
                                                     
                                         
                                                             




                                          
                                                
 
                                   





                                                                 
                                                                   






                                                                                               
                                          
                                                                
                         







                                                                                                                 
                                                                   
                                                                        

                                                      
                                                                                  
                                                  

                                                              
                    


                                                                                                                      
      

                         
                           
 


                                                               
                                                                         
                                                                       
                                                                                              
                                         
 
                           
                                 
                                         
 





                                                                            
                                  















                                                                                            
                                                     




                                                                                
                                                                                                       





                                                                          

                               
 
                                                                                                       
                                    
                                          
 
 


























                                                                                        
                                                              





                                                                      
                                                        
                                                                                 













                                                                        
                                            




                                 




                                                                           
                                                                       
      
                                


                                   
                                            
                            

                                                                         
                                                                   




                                                                                
                                                          
                           
                                                    




                                          
#include <libintl.h>
#include <glib/gi18n.h>
#include <libsoup/soup.h>
#include <libxml/tree.h>
#include <libxml/parser.h>
#include "dive.h"
#include "divelist.h"
#include "display-gtk.h"
#include "file.h"

struct dive_table gps_location_table;
static gboolean merge_locations_into_dives(void);

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: ");
	case DD_STATUS_ERROR_ID:
		return _("Invalid user identifier!");
	case DD_STATUS_ERROR_PARSE:
		return _("Cannot parse response!");
	}
	return _("Download Success!");
}

/* provides a state of the download dialog contents and the downloaded xml */
struct download_dialog_state {
	GtkWidget *uid;
	GtkWidget *status;
	GtkWidget *apply;
	gchar *xmldata;
	guint xmldata_len;
};

/* 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[256] = {0};

	session = soup_session_async_new();
	strcat(url, "http://api.hohndel.org/api/dive/get/?login=");
	strncat(url, user_id, sizeof(url) - strlen(url) - 1);
	msg = soup_message_new("GET", url);
	soup_message_headers_append(msg->request_headers, "Accept", "text/xml");
	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;
}

/* requires that there is a <download> or <error> tag under the <root> tag */
static void download_dialog_traverse_xml(xmlNodePtr node, guint *download_status)
{
	xmlNodePtr cur_node;
	for (cur_node = node; cur_node; cur_node = cur_node->next) {
		if ((!strcmp((const char *)cur_node->name, (const char *)"download")) &&
			  (!strcmp((const char *)xmlNodeGetContent(cur_node), (const char *)"ok"))) {
			*download_status = DD_STATUS_OK;
			return;
		}	else if (!strcmp((const char *)cur_node->name, (const char *)"error")) {
			*download_status = DD_STATUS_ERROR_ID;
			return;
		}
	}
}

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;
	}
	if (root->children)
		download_dialog_traverse_xml(root->children, &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[256] = {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 {
		snprintf(err, sizeof(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;
	state->xmldata_len = len;
	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 clear_table(struct dive_table *table)
{
	int i;
	for (i = 0; i < table->nr; i++)
		free(table->dives[i]);
	table->nr = 0;
}

static void download_dialog_response_cb(GtkDialog *d, gint response, gpointer data)
{
	struct download_dialog_state *state = (struct download_dialog_state *)data;
	switch (response) {
	case GTK_RESPONSE_HELP:
		/* open webservice api page */
		subsurface_launch_for_uri("http://api.hohndel.org/");
		break;
	case GTK_RESPONSE_ACCEPT:
		/* apply download */
		clear_table(&gps_location_table);
		parse_xml_buffer(_("Webservice"), state->xmldata, state->xmldata_len, &gps_location_table, NULL);
		/* now merge the data in the gps_location table into the dive_table */
		if (merge_locations_into_dives()) {
			mark_divelist_changed(TRUE);
#if USE_GTK_UI
			dive_list_update_dives();
#endif
		}
		/* store last entered uid in config */
		subsurface_set_conf("webservice_uid", gtk_entry_get_text(GTK_ENTRY(state->uid)));
	default:
	case GTK_RESPONSE_DELETE_EVENT:
		gtk_widget_destroy(GTK_WIDGET(d));
		download_dialog_release_xml(state);
		free(state);
	}
}

static gboolean is_automatic_fix(struct dive *gpsfix)
{
	if (gpsfix && gpsfix->location &&
	    (!strcmp(gpsfix->location, "automatic fix") ||
	     !strcmp(gpsfix->location, "Auto-created dive")))
		return TRUE;
	return FALSE;
}

#define SAME_GROUP 6 * 3600   // six hours

/* returns TRUE if dive_table was changed */
static gboolean merge_locations_into_dives(void)
{
	int i, nr = 0, changed = 0;
	struct dive *gpsfix, *last_named_fix = NULL, *dive;

	sort_table(&gps_location_table);

	for_each_gps_location(i, gpsfix) {
		if (is_automatic_fix(gpsfix)) {
			dive = find_dive_including(gpsfix->when);
			if (dive && !dive_has_gps_location(dive)) {
#if DEBUG_WEBSERVICE
				struct tm tm;
				utc_mkdate(gpsfix->when, &tm);
				printf("found dive named %s @ %04d-%02d-%02d %02d:%02d:%02d\n",
					gpsfix->location,
					tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
					tm.tm_hour, tm.tm_min, tm.tm_sec);
#endif
				changed++;
				copy_gps_location(gpsfix, dive);
			}
		} else {
			if (last_named_fix && dive_within_time_range(last_named_fix, gpsfix->when, SAME_GROUP)) {
				nr++;
			} else {
				nr = 1;
				last_named_fix = gpsfix;
			}
			dive = find_dive_n_near(gpsfix->when, nr, SAME_GROUP);
			if (dive) {
				if (!dive_has_gps_location(dive)) {
					copy_gps_location(gpsfix, dive);
					changed++;
				}
				if (!dive->location) {
					dive->location = strdup(gpsfix->location);
					changed++;
				}
			} else {
				struct tm tm;
				utc_mkdate(gpsfix->when, &tm);
#if DEBUG_WEBSERVICE
				printf("didn't find dive matching gps fix named %s @ %04d-%02d-%02d %02d:%02d:%02d\n",
					gpsfix->location,
					tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
					tm.tm_hour, tm.tm_min, tm.tm_sec);
#endif
			}
		}
	}
	return changed > 0;
}

void webservice_download_dialog(void)
{
	const guint pad = 6;
	/* user entered value should be stored in the config */
	const gchar *current_uid = subsurface_get_conf("webservice_uid");
	GtkWidget *dialog, *vbox, *status, *info, *uid;
	GtkWidget *frame_uid, *frame_status, *download, *image, *apply;
	struct download_dialog_state *state = calloc(1, sizeof(struct download_dialog_state));
	gboolean has_previous_uid = TRUE;

	if (!current_uid) {
		current_uid = "";
		has_previous_uid = FALSE;
	}

	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,
		GTK_STOCK_HELP,
		GTK_RESPONSE_HELP,
		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_max_length(GTK_ENTRY(uid), 30);
	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), (gpointer)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;

	g_signal_connect(dialog, "response", G_CALLBACK(download_dialog_response_cb), (gpointer)state);
	gtk_widget_show_all(dialog);
	if (has_previous_uid)
		free((void *)current_uid);
}

static gboolean divelogde_dialog(const char **user, const char **pass)
{
	GtkWidget *dialog, *vbox, *info, *frame_user, *frame_pass, *uid, *pwd;
	gboolean ret = FALSE;

	*user = subsurface_get_conf("divelogde_user");
	*pass = subsurface_get_conf("divelogde_pass");
	dialog = gtk_dialog_new_with_buttons(_("Upload to divelogs.de"),
		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);
	vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
	info = gtk_label_new(_("Please enter your userid and password for divelogs.de. "
				"The selected dives will be added to your account"));
	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), 6, 6);

	frame_user = gtk_frame_new(_("User Identifier"));
	gtk_box_pack_start(GTK_BOX(vbox), frame_user, FALSE, TRUE, 6);
	uid = gtk_entry_new();
	gtk_container_add(GTK_CONTAINER(frame_user), uid);
	gtk_entry_set_max_length(GTK_ENTRY(uid), 40);
	gtk_entry_set_text(GTK_ENTRY(uid), *user ?: "");
	gtk_entry_set_activates_default(GTK_ENTRY(uid), TRUE);

	frame_pass = gtk_frame_new(_("Password"));
	gtk_box_pack_start(GTK_BOX(vbox), frame_pass, FALSE, TRUE, 6);
	pwd = gtk_entry_new();
	gtk_container_add(GTK_CONTAINER(frame_pass), pwd);
	gtk_entry_set_max_length(GTK_ENTRY(pwd), 40);
	gtk_entry_set_text(GTK_ENTRY(pwd), *pass ?: "");
	gtk_entry_set_visibility(GTK_ENTRY(pwd), FALSE);
	gtk_entry_set_activates_default(GTK_ENTRY(pwd), TRUE);
	gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);

	gtk_widget_show_all(dialog);
	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
		free((void*)*user);
		free((void*)*pass);
		*user = strdup(gtk_entry_get_text(GTK_ENTRY(uid)));
		*pass = strdup(gtk_entry_get_text(GTK_ENTRY(pwd)));
		subsurface_set_conf("divelogde_user", *user);
		subsurface_set_conf("divelogde_pass", *pass);
		ret = TRUE;
	}
	gtk_widget_destroy(dialog);
	return ret;
}

int divelogde_upload(char *fn, char **error)
{
	SoupMessage *msg;
	SoupMultipart *multipart;
	SoupSession *session;
	SoupBuffer *sbuf;
	gboolean ret = FALSE;
#ifdef __MINGW32__
	/* for odd reasons I can't seem to get https connections to work
	 * with mingw32 when cross-building the Windows binaries... so fall
	 * back to http for now */
	char url[256] = "http://divelogs.de/DivelogsDirectImport.php";
#else
	char url[256] = "https://divelogs.de/DivelogsDirectImport.php";
#endif
	const char *pass = NULL;
	const char *user = NULL;
	struct memblock mem;

	if (readfile(fn, &mem) < 0)
		return ret;
	if (!divelogde_dialog(&user, &pass))
		return TRUE;
	sbuf = soup_buffer_new(SOUP_MEMORY_STATIC, mem.buffer, mem.size);
	session = soup_session_async_new();
	multipart = soup_multipart_new(SOUP_FORM_MIME_TYPE_MULTIPART);
	soup_multipart_append_form_string(multipart, "user", user);
	soup_multipart_append_form_string(multipart, "pass", pass);

	soup_multipart_append_form_file(multipart, "userfile", fn, NULL, sbuf);
	msg = soup_form_request_new_from_multipart(url, multipart);
	soup_message_headers_append(msg->request_headers, "Accept", "text/xml");
	soup_session_send_message(session, msg);
	if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) {
		*error = strdup(msg->response_body->data);
		ret = TRUE;
	} else {
		*error = strdup(msg->reason_phrase);
	}
	soup_session_abort(session);
	g_object_unref(G_OBJECT(msg));
	g_object_unref(G_OBJECT(session));
	return ret;
}