Daily update.
authorRichard Jones <rjones@redhat.com>
Fri, 28 Nov 2008 15:20:11 +0000 (15:20 +0000)
committerRichard Jones <rjones@redhat.com>
Fri, 28 Nov 2008 15:20:11 +0000 (15:20 +0000)
internal.h
main.c
wui_thread.c

index d838fde..2fbf2c7 100644 (file)
@@ -83,16 +83,30 @@ extern void wui_thread_send_refresh_vm_list (void);
 
 /* Retrieve the list of VMs.
  *
+ * wui_thread_get_vmlist returns TRUE if there was a valid list of
+ * VMs (even if it is empty), or FALSE if we don't have a valid list
+ * of VMs to return.
+ *
  * NB: Caller must call free_vmlist once it is done with the list.
  *
- * This can return NULL if the WUI thread doesn't have a valid
- * list of VMs to return to the caller.  In the case of a valid,
- * empty list, you will get a non-NULL GSList pointer to an empty
- * list.
  */
-extern GSList *wui_thread_get_vmlist (void);
+extern gboolean wui_thread_get_vmlist (GSList **ret);
 extern void free_vmlist (GSList *vmlist);
 
+struct vm {
+  char *description;
+  int hostid;
+  int id;
+  long mem_allocated;          /* Kbytes */
+  long mem_used;               /* Kbytes */
+  int vcpus_allocated;
+  int vcpus_used;
+  char *state;
+  char *uuid;                  /* Printable UUID. */
+  int vnc_port;
+  char *mac_addr;              /* Printable MAC addr. */
+};
+
 /* Returns true if the WUI thread thinks it is connected to a remote
  * WUI.  REST is connectionless so really this means that we
  * successfully made an HTTP/HTTPS request "recently", and we haven't
@@ -117,4 +131,36 @@ extern gboolean wui_thread_has_valid_vmlist (void);
  */
 extern gboolean wui_thread_is_busy (void);
 
+/* Callbacks from the WUI thread to the main thread.  The WUI thread
+ * adds these to the Glib main loop using g_idle_add, which means they
+ * actually get executed in the context of the main thread.
+ */
+
+/* The WUI thread has changed its state to connected. */
+extern gboolean main_connected (gpointer);
+
+/* The WUI thread has changed its state to disconnected. */
+extern gboolean main_disconnected (gpointer);
+
+/* The WUI thread has changed its state to logged in. */
+extern gboolean main_logged_in (gpointer);
+
+/* The WUI thread has changed its state to logged out. */
+extern gboolean main_logged_out (gpointer);
+
+/* The WUI thread has changed its state to busy. */
+extern gboolean main_busy (gpointer);
+
+/* The WUI thread has changed its state to idle. */
+extern gboolean main_idle (gpointer);
+
+/* The WUI thread had a connection problem. */
+extern gboolean main_connection_error (gpointer str);
+
+/* The WUI thread had a login problem. */
+extern gboolean main_login_error (gpointer str);
+
+/* The WUI thread reports a general status error. */
+extern gboolean main_status_error (gpointer str);
+
 #endif /* OVIRT_VIEWER_INTERNAL_H */
diff --git a/main.c b/main.c
index 1761864..0545ec4 100644 (file)
--- a/main.c
+++ b/main.c
 #include <windows.h>
 #endif
 
-#ifndef G_THREADS_ENABLED
-#error "This program requires GLib threads, and cannot be compiled without."
-#endif
-
 #include "internal.h"
 
 /*#define HTTPS "https"*/
@@ -60,17 +56,21 @@ gboolean check_cert = TRUE;
 static void start_ui (void);
 static GtkWidget *menu_item_new (int which_menu);
 static void connect_to_wui (GtkWidget *, gpointer);
+static void login_to_wui (GtkWidget *, gpointer);
 static gboolean delete_event (GtkWidget *widget, GdkEvent *event, gpointer data);
 static void destroy (GtkWidget *widget, gpointer data);
 
 /* For any widgets accessed from multiple functions. */
+static GtkWidget *window;
 static GtkWidget *connection_area;
 static GtkWidget *ca_hostname;
 static GtkWidget *ca_button;
+static GtkWidget *ca_error;
 static GtkWidget *login_area;
 static GtkWidget *la_username;
 static GtkWidget *la_password;
 static GtkWidget *la_button;
+static GdkCursor *busy_cursor;
 
 /* Menus. */
 enum menuNums {
@@ -201,7 +201,6 @@ main (int argc, char *argv[])
 static void
 start_ui (void)
 {
-  GtkWidget *window;
   GtkWidget *vbox;
   GtkWidget *menubar;
   GtkWidget *file;
@@ -225,6 +224,11 @@ start_ui (void)
   /* Parse styles. */
   gtk_rc_parse_string (styles);
 
+  /* Busy cursor, used by main_busy() function.
+   * XXX This cursor is crap - how can we use the Bluecurve/theme cursor?
+   */
+  busy_cursor = gdk_cursor_new (GDK_WATCH);
+
   /* Window. */
   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
   gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
@@ -282,10 +286,12 @@ start_ui (void)
   ca_hostname = gtk_entry_new ();
   gtk_entry_set_width_chars (GTK_ENTRY (ca_hostname), 24);
   ca_button = gtk_button_new_with_label ("Connect");
+  ca_error = gtk_label_new (NULL);
   gtk_box_pack_start (GTK_BOX (ca_hbox), ca_hostname, FALSE, FALSE, 0);
   gtk_box_pack_start (GTK_BOX (ca_hbox), ca_button,   FALSE, FALSE, 0);
   gtk_box_pack_start (GTK_BOX (ca_vbox), ca_label,    FALSE, FALSE, 4);
   gtk_box_pack_start (GTK_BOX (ca_vbox), ca_hbox,     TRUE,  FALSE, 4);
+  gtk_box_pack_start (GTK_BOX (ca_vbox), ca_error,    TRUE,  FALSE, 4);
   gtk_container_add (GTK_CONTAINER (connection_area), ca_vbox);
 
   gtk_widget_set_name (connection_area, "ovirt-viewer-connection-area");
@@ -308,6 +314,9 @@ start_ui (void)
 
   gtk_widget_set_name (login_area, "ovirt-viewer-login-area");
 
+  g_signal_connect (G_OBJECT (la_button), "clicked",
+                   G_CALLBACK (login_to_wui), NULL);
+
   /* Tabbed notebook. */
   notebook = gtk_notebook_new ();
   gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_LEFT);
@@ -388,3 +397,144 @@ connect_to_wui (GtkWidget *widget, gpointer data)
 
   wui_thread_send_connect (uri);
 }
+
+static void
+login_to_wui (GtkWidget *widget, gpointer data)
+{
+  const char *username, *password;
+
+  username = gtk_entry_get_text (GTK_ENTRY (la_username));
+  if (STREQ (username, "")) return;
+  password = gtk_entry_get_text (GTK_ENTRY (la_password));
+
+  wui_thread_send_login (username, password);
+}
+
+/* The WUI thread has changed its state to connected. */
+gboolean
+main_connected (gpointer data)
+{
+  DEBUG ("connected");
+
+  gtk_label_set_text (GTK_LABEL (ca_error), NULL);
+
+  gtk_widget_hide (connection_area);
+  if (!wui_thread_is_logged_in ())
+    gtk_widget_show (login_area);
+  return FALSE;
+}
+
+/* The WUI thread has changed its state to disconnected. */
+gboolean
+main_disconnected (gpointer data)
+{
+  DEBUG ("disconnected");
+  gtk_widget_show (connection_area);
+  gtk_widget_hide (login_area);
+  return FALSE;
+}
+
+/* The WUI thread has changed its state to logged in. */
+gboolean
+main_logged_in (gpointer data)
+{
+  DEBUG ("logged in");
+  gtk_widget_hide (login_area);
+  return FALSE;
+}
+
+/* The WUI thread has changed its state to logged out. */
+gboolean
+main_logged_out (gpointer data)
+{
+  DEBUG ("logged out");
+  if (wui_thread_is_connected ())
+    gtk_widget_show (login_area);
+  return FALSE;
+}
+
+/* The WUI thread has changed its state to busy. */
+gboolean
+main_busy (gpointer data)
+{
+  GdkWindow *gdk_window;
+
+  DEBUG ("busy");
+
+  gdk_window = gtk_widget_get_window (window);
+  if (gdk_window) {
+    gdk_window_set_cursor (gdk_window, busy_cursor);
+    gdk_flush ();
+  }
+
+  return FALSE;
+}
+
+/* The WUI thread has changed its state to idle. */
+gboolean
+main_idle (gpointer data)
+{
+  GdkWindow *gdk_window;
+
+  DEBUG ("idle");
+
+  gdk_window = gtk_widget_get_window (window);
+  if (gdk_window) {
+    gdk_window_set_cursor (gdk_window, NULL);
+    gdk_flush ();
+  }
+
+  return FALSE;
+}
+
+/* The WUI thread had a connection error.  This function must
+ * free the string.
+ */
+gboolean
+main_connection_error (gpointer _str)
+{
+  char *str = (char *) _str;
+
+  DEBUG ("connection error: %s", str);
+
+  gtk_label_set_text (GTK_LABEL (ca_error), str);
+  g_free (str);
+
+  return FALSE;
+}
+
+/* The WUI thread had a login error.  This function must
+ * free the string.
+ */
+gboolean
+main_login_error (gpointer _str)
+{
+  char *str = (char *) _str;
+
+  DEBUG ("login error: %s", str);
+
+  /*
+  gtk_label_set_text (GTK_LABEL (ca_error), str);
+  */
+  g_free (str);
+
+  return FALSE;
+}
+
+/* The WUI thread reports a general status error.  This function
+ * must free the string.
+ */
+gboolean
+main_status_error (gpointer _str)
+{
+  char *str = (char *) _str;
+
+  DEBUG ("status error: %s", str);
+
+  /*
+  gtk_label_set_text (GTK_LABEL (ca_error), str);
+  */
+  g_free (str);
+
+  return FALSE;
+}
index 8079f75..49dc270 100644 (file)
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 #include <assert.h>
 
 #include <glib.h>
+#include <glib/gprintf.h>
 
 #include <curl/curl.h>
 
 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)
+{
+  
+
+
+
+
+
+
 }