X-Git-Url: http://git.annexia.org/?a=blobdiff_plain;f=wui_thread.c;fp=wui_thread.c;h=49dc2709aa18c9c47bba857be534e841626474c3;hb=dc0e64a4c244fa084eb7bfce4503404824ada6ef;hp=8079f7504508d36c16b2f2a241d4642018c54b11;hpb=9e71dc8c3f23425cdd4453402b7c2ba204a6e5e0;p=ovirt-viewer.git diff --git a/wui_thread.c b/wui_thread.c index 8079f75..49dc270 100644 --- a/wui_thread.c +++ b/wui_thread.c @@ -21,9 +21,11 @@ #include #include +#include #include #include +#include #include @@ -33,9 +35,13 @@ static gpointer wui_thread (gpointer data); static void wui_thread_send_quit (void); static void do_curl_init (void); +static void write_fn_start_capture (void); +static char *write_fn_finish_capture (void); +static void write_fn_discard_capture_buffer (void); static gboolean do_connect (void); static gboolean do_login (void); -static void refresh_vm_list (void); +static gboolean refresh_vm_list (void); +static void parse_vmlist_from_xml (const char *xml); /* Messages (main thread -> WUI thread only). * @@ -251,6 +257,11 @@ set_connected (gboolean new_connected) g_static_mutex_lock (&state_mutex); connected = new_connected; g_static_mutex_unlock (&state_mutex); + + if (connected) + g_idle_add (main_connected, NULL); + else + g_idle_add (main_disconnected, NULL); } static void @@ -259,6 +270,11 @@ set_logged_in (gboolean new_logged_in) g_static_mutex_lock (&state_mutex); logged_in = new_logged_in; g_static_mutex_unlock (&state_mutex); + + if (logged_in) + g_idle_add (main_logged_in, NULL); + else + g_idle_add (main_logged_out, NULL); } static void @@ -267,6 +283,11 @@ set_busy (gboolean new_busy) g_static_mutex_lock (&state_mutex); busy = new_busy; g_static_mutex_unlock (&state_mutex); + + if (busy) + g_idle_add (main_busy, NULL); + else + g_idle_add (main_idle, NULL); } /* The main thread should call these functions to get the WUI thread's @@ -311,6 +332,7 @@ process_message (struct message *msg) { switch (msg->type) { case QUIT: + write_fn_discard_capture_buffer (); if (curl) curl_easy_cleanup (curl); if (uri) g_free (uri); if (username) g_free (username); @@ -320,6 +342,7 @@ process_message (struct message *msg) return 1; case CONNECT: + write_fn_discard_capture_buffer (); if (curl) curl_easy_cleanup (curl); do_curl_init (); if (uri) g_free (uri); @@ -335,6 +358,7 @@ process_message (struct message *msg) case DISCONNECT: /* This just forgets the state. REST is connectionless. */ + write_fn_discard_capture_buffer (); if (curl) curl_easy_cleanup (curl); curl = NULL; if (uri) g_free (uri); @@ -360,7 +384,8 @@ process_message (struct message *msg) if (connected) { if (do_login ()) { set_logged_in (TRUE); - refresh_vm_list (); + if (refresh_vm_list ()) + secs_between_refresh = 60; } else { set_logged_in (FALSE); } @@ -392,13 +417,60 @@ process_message (struct message *msg) __r; \ }) +/* Libcurl has a really crufty method for handling HTTP headers and + * data. We set these functions as callbacks, because the default + * callback functions print the data out to stderr. In order to + * capture the data, we have to keep state here. + */ + +static char *write_fn_buffer = NULL; +static ssize_t write_fn_len = -1; + static size_t write_fn (void *ptr, size_t size, size_t nmemb, void *stream) { int bytes = size * nmemb; + int old_start; + + if (write_fn_len >= 0) { /* We're capturing. */ + old_start = write_fn_len; + write_fn_len += bytes; + write_fn_buffer = g_realloc (write_fn_buffer, write_fn_len); + memcpy (write_fn_buffer + old_start, ptr, bytes); + } + return bytes; } +/* Start capturing HTTP response data. */ +static void +write_fn_start_capture (void) +{ + write_fn_discard_capture_buffer (); + write_fn_buffer = NULL; + write_fn_len = 0; +} + +/* Finish capture and return the capture buffer. Caller must free. */ +static char * +write_fn_finish_capture (void) +{ + char *ret = write_fn_buffer; + + write_fn_buffer = NULL; + write_fn_len = -1; + return ret; +} + +/* Stop capturing and discard the capture buffer, if any. */ +static void +write_fn_discard_capture_buffer (void) +{ + g_free (write_fn_buffer); + write_fn_buffer = NULL; + write_fn_len = -1; +} + static size_t header_fn (void *ptr, size_t size, size_t nmemb, void *stream) { @@ -446,6 +518,8 @@ static gboolean do_connect (void) { long code = 0; + CURLcode r; + char *error_str; DEBUG ("connecting to uri %s", uri); @@ -453,13 +527,22 @@ do_connect (void) CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, uri)); /* Try to fetch the URI. */ - if (CURL_CHECK_ERROR (curl_easy_perform, (curl)) != CURLE_OK) + r = CURL_CHECK_ERROR (curl_easy_perform, (curl)); + if (r != CURLE_OK) { + /* Signal an error back to the main thread. */ + error_str = g_strdup (curl_easy_strerror (r)); + g_idle_add (main_connection_error, error_str); return FALSE; + } CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code)); DEBUG ("HTTP return code is %ld", code); - if (code != 200 || code != 302 || code != 401) + if (code != 200 && code != 302 && code != 401) { + /* XXX If only glib had g_asprintf. */ + error_str = g_strdup ("unexpected HTTP return code from server"); + g_idle_add (main_connection_error, error_str); return FALSE; + } return TRUE; } @@ -470,13 +553,252 @@ do_connect (void) static gboolean do_login (void) { + int len; + char *login_uri; + char *userpwd; + char *error_str; + CURLcode r; + long code = 0; + DEBUG ("logging in with username %s, password *****", username); - return FALSE; + + /* Generate the login URI from the base URI. */ + len = strlen (uri) + 6 + 1; + login_uri = g_alloca (len); + snprintf (login_uri, len, "%s/login", uri); + + DEBUG ("login URI %s", login_uri); + + /* Set the URI for libcurl. */ + CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, login_uri)); + + /* Construct the username:password for CURL. */ + len = strlen (username) + strlen (password) + 2; + userpwd = g_alloca (len); + snprintf (userpwd, len, "%s:%s", username, password); + + CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_USERPWD, userpwd)); + + /* HTTP Basic authentication is OK since we should be sending + * this only over HTTPS. + */ + CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC)); + + /* Follow redirects. */ + CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_FOLLOWLOCATION, (long) 1)); + CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_MAXREDIRS, (long) 10)); + + /* Try to fetch the URI. */ + r = CURL_CHECK_ERROR (curl_easy_perform, (curl)); + if (r != CURLE_OK) { + /* Signal an error back to the main thread. */ + error_str = g_strdup (curl_easy_strerror (r)); + g_idle_add (main_login_error, error_str); + return FALSE; + } + + CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code)); + DEBUG ("HTTP return code is %ld", code); + switch (code) + { + case 200: + DEBUG ("login was successful"); + return TRUE; + + case 401: + error_str = g_strdup ("server rejected the username or password"); + g_idle_add (main_login_error, error_str); + return FALSE; + + default: + /* XXX If only glib had g_asprintf. */ + error_str = g_strdup ("unexpected HTTP return code from server"); + g_idle_add (main_login_error, error_str); + return FALSE; + } } /* Called from the message loop. Refresh the list of VMs. */ -static void +static gboolean refresh_vm_list (void) { + int len; + char *vms_uri; + char *error_str; + CURLcode r; + long code = 0; + char *xml; + DEBUG ("refreshing list of VMs"); + + /* Generate the vms URI from the base URI. */ + len = strlen (uri) + 4 + 1; + vms_uri = g_alloca (len); + snprintf (vms_uri, len, "%s/vms", uri); + + DEBUG ("vms URI %s", vms_uri); + + /* Set the URI for libcurl. */ + CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, vms_uri)); + + /* We want to capture the output, so tell our write function + * to place the output into a buffer. + */ + write_fn_start_capture (); + + /* Try to fetch the URI. */ + r = CURL_CHECK_ERROR (curl_easy_perform, (curl)); + if (r != CURLE_OK) { + /* Signal an error back to the main thread. */ + error_str = g_strdup (curl_easy_strerror (r)); + g_idle_add (main_login_error, error_str); + return FALSE; + } + + CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code)); + DEBUG ("HTTP return code is %ld", code); + switch (code) + { + case 200: break; + + /* Hmm - even though we previously logged in, the server is + * rejecting our attempts now with an authorization error. + * We move to the logged out state. + */ + case 401: + set_logged_in (FALSE); + error_str = g_strdup ("server rejected the username or password"); + g_idle_add (main_login_error, error_str); + return FALSE; + + default: + /* XXX If only glib had g_asprintf. */ + error_str = g_strdup ("unexpected HTTP return code from server"); + g_idle_add (main_status_error, error_str); + return FALSE; + } + + /* If we got here then we appear to have a correct + * XML document describing the list of VMs. + */ + secs_between_refresh <<= 1; + + xml = write_fn_finish_capture (); + + /*DEBUG ("XML from /vms =\n%s", xml);*/ + parse_vmlist_from_xml (xml); + g_free (xml); + + return TRUE; +} + +/* Functions to deal with the list of VMs, parsing it from the XML, etc. + * + * A vmlist is a GSList (glib singly-linked list) of vm structures. + * The caller must call free_vmlist to free up this list correctly. + */ + +static void +free_vm (gpointer _vm, gpointer data) +{ + struct vm *vm = (struct vm *) _vm; + + g_free (vm->description); + g_free (vm->state); + g_free (vm->uuid); + g_free (vm->mac_addr); + g_free (vm); +} + +void +free_vmlist (GSList *vmlist) +{ + g_slist_foreach (vmlist, free_vm, NULL); + g_slist_free (vmlist); +} + +static struct vm * +copy_vm (struct vm *vm) +{ + struct vm *vm2; + + vm2 = g_memdup (vm, sizeof (*vm)); + vm2->description = g_strdup (vm->description); + vm2->state = g_strdup (vm->state); + vm2->uuid = g_strdup (vm->uuid); + vm2->mac_addr = g_strdup (vm->mac_addr); + return vm2; +} + +static int +compare_vm (gconstpointer _vm1, gconstpointer _vm2) +{ + const struct vm *vm1 = (const struct vm *) _vm1; + const struct vm *vm2 = (const struct vm *) _vm2; + + return strcmp (vm1->description, vm2->description); +} + +static GSList *vmlist = NULL; +static gboolean vmlist_valid = FALSE; +static GStaticMutex vmlist_mutex; + +/* Called from the main thread to find out if we have a valid vmlist. */ +gboolean +wui_thread_has_valid_vmlist (void) +{ + gboolean ret; + + g_static_mutex_lock (&vmlist_mutex); + ret = vmlist_valid; + g_static_mutex_unlock (&vmlist_mutex); + return ret; +} + +/* Called from the main thread to find return the current vmlist. This + * actually returns a deep copy of it that the caller must free. + */ +static void +duplicate_and_insert_vm (gpointer _vm, gpointer _ret) +{ + struct vm *vm = (struct vm *) _vm; + GSList **ret = (GSList **) _ret; + + *ret = g_slist_prepend (*ret, copy_vm (vm)); +} + +gboolean +wui_thread_get_vmlist (GSList **ret) +{ + gboolean r; + + r = FALSE; + *ret = NULL; + + g_static_mutex_lock (&vmlist_mutex); + if (!vmlist_valid) goto done; + + g_slist_foreach (vmlist, duplicate_and_insert_vm, ret); + *ret = g_slist_sort (*ret, compare_vm); + + r = TRUE; + done: + g_static_mutex_unlock (&vmlist_mutex); + return r; +} + +/* Called from the message loop in the WUI thread, with an XML document + * which we turn into a list of VM structures, and update the vmlist + * if we can. + */ +static void +parse_vmlist_from_xml (const char *xml) +{ + + + + + + + }