Restructure main program and separate thread for WUI interaction.
[ovirt-viewer.git] / wui_thread.c
diff --git a/wui_thread.c b/wui_thread.c
new file mode 100644 (file)
index 0000000..0db43c8
--- /dev/null
@@ -0,0 +1,389 @@
+/* ovirt viewer console application
+ * Copyright (C) 2008 Red Hat Inc.
+ * Written by Richard W.M. Jones <rjones@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include <glib.h>
+
+#include "internal.h"
+
+/* Private functions. */
+static gpointer wui_thread (gpointer data);
+static void wui_thread_send_quit (void);
+static gboolean do_connect (void);
+static gboolean do_login (void);
+static void refresh_vm_list (void);
+
+/* Messages (main thread -> WUI thread only).
+ *
+ * These are passed by reference.  They are allocated by the sender
+ * (ie. the main thread) and freed by the receiver (ie. the WUI thread).
+ */
+enum message_type {
+  QUIT,                           /* Tell the WUI thread to quit cleanly. */
+  CONNECT,                /* Tell to connect (just fetch the URI). */
+  DISCONNECT,             /* Tell to disconnect, forget state. */
+  LOGIN,                  /* Tell to login, and pass credentials. */
+  REFRESH_VM_LIST,        /* Tell to refresh the VM list right away. */
+};
+
+struct message {
+  enum message_type type;
+  char *str1;
+  char *str2;
+};
+
+/* Start the WUI thread.  See main() for explanation of the threading model. */
+static GThread *wui_gthread = NULL;
+static GAsyncQueue *wui_thread_queue = NULL;
+
+void
+start_wui_thread (void)
+{
+  GError *error = NULL;
+
+  DEBUG ("starting the WUI thread");
+
+  /* Create the message queue for main -> WUI thread communications. */
+  wui_thread_queue = g_async_queue_new ();
+
+  wui_gthread = g_thread_create (wui_thread, wui_thread_queue, TRUE, &error);
+  if (error) {
+    g_print ("%s\n", error->message);
+    g_error_free (error);
+    exit (1);
+  }
+}
+
+void
+stop_wui_thread (void)
+{
+  DEBUG ("stopping the WUI thread");
+  assert (wui_gthread);
+
+  /* Send a quit message then wait for the WUI thread to join.
+   *
+   * This "nice" shutdown could cause the UI to hang for an
+   * indefinite period (eg. if the WUI thread is engaged in some
+   * long connection or download from the remote server).  But
+   * I want to keep it this way for the moment so that we can
+   * diagnose problems with the WUI thread.
+   *
+   * (This could be solved with some sort of interruptible
+   * join, but glib doesn't support that AFAICT).
+   */
+  wui_thread_send_quit ();
+  (void) g_thread_join (wui_gthread);
+  g_async_queue_unref (wui_thread_queue);
+  wui_gthread = NULL;
+}
+
+/* Send the quit message to the WUI thread. */
+static void
+wui_thread_send_quit (void)
+{
+  struct message *msg;
+
+  msg = g_new (struct message, 1);
+  msg->type = QUIT;
+  g_async_queue_push (wui_thread_queue, msg);
+}
+
+/* Send the connect message to the WUI thread. */
+void
+wui_thread_send_connect (const char *uri)
+{
+  struct message *msg;
+
+  msg = g_new (struct message, 1);
+  msg->type = CONNECT;
+  msg->str1 = g_strdup (uri);
+  g_async_queue_push (wui_thread_queue, msg);
+}
+
+/* Send the disconnect message to the WUI thread. */
+void
+wui_thread_send_disconnect (void)
+{
+  struct message *msg;
+
+  msg = g_new (struct message, 1);
+  msg->type = DISCONNECT;
+  g_async_queue_push (wui_thread_queue, msg);
+}
+
+/* Send the login message to the WUI thread. */
+void
+wui_thread_send_login (const char *username, const char *password)
+{
+  struct message *msg;
+
+  msg = g_new (struct message, 1);
+  msg->type = LOGIN;
+  msg->str1 = g_strdup (username);
+  msg->str2 = g_strdup (password);
+  g_async_queue_push (wui_thread_queue, msg);
+}
+
+/* Send the refresh VM list message to the WUI thread. */
+void
+wui_thread_send_refresh_vm_list (void)
+{
+  struct message *msg;
+
+  msg = g_new (struct message, 1);
+  msg->type = REFRESH_VM_LIST;
+  g_async_queue_push (wui_thread_queue, msg);
+}
+
+/* The current state.
+ *
+ * For safety, the main thread must lock this before reading, and the
+ * WUI thread must lock this before writing.  However the WUI thread
+ * does not need to lock before reading, because no other thread
+ * can modify it.
+ */
+static gboolean connected = FALSE;
+static gboolean logged_in = FALSE;
+static gboolean busy = FALSE;
+static GStaticMutex state_mutex;
+
+static void set_connected (gboolean);
+static void set_logged_in (gboolean);
+static void set_busy (gboolean);
+
+/* The private state of the WUI thread. */
+static int secs_between_refresh = 60;
+static char *uri = NULL;
+static char *username = NULL;
+static char *password = NULL;
+
+static gboolean process_message (struct message *);
+
+/* The WUI thread.  See main() above for explanation of
+ * the threading model.
+ */
+static gpointer
+wui_thread (gpointer _queue)
+{
+  GAsyncQueue *queue = (GAsyncQueue *) _queue;
+  gboolean quit = FALSE;
+  GTimeVal tv;
+  gpointer _msg;
+  struct message *msg;
+
+  DEBUG ("WUI thread starting up");
+
+  g_async_queue_ref (queue);
+
+  /* In the thread's loop we check for new instructions from the main
+   * thread and carry them out.  Also, if we are connected and logged
+   * in then we periodically recheck the list of VMs.
+   */
+  while (!quit) {
+    set_busy (FALSE);
+
+    if (logged_in) {
+      g_get_current_time (&tv);
+      g_time_val_add (&tv, secs_between_refresh * 1000000);
+      _msg = g_async_queue_timed_pop (queue, &tv);
+    } else
+      _msg = g_async_queue_pop (queue);
+
+    set_busy (TRUE);
+
+    msg = (struct message *) _msg;
+
+    if (msg) {
+      DEBUG ("received message %d", msg->type);
+      quit = process_message (msg);
+      /* Don't free any strings in the message - we've saved them. */
+      g_free (msg);
+    } else {
+      /* No message, must have got a timeout instead, which means
+       * we are logged in and we should refresh the list of VMs.
+       * Note it's not an error if we temporarily lose contact
+       * with the WUI.
+       */
+      refresh_vm_list ();
+    }
+  }
+
+  DEBUG ("WUI thread shutting down cleanly");
+
+  g_async_queue_unref (queue);
+  g_thread_exit (NULL);
+  return NULL; /* not reached? */
+}
+
+/* The WUI thread calls this to safely update the state variables.
+ * This also updates elements in the UI by setting idle callbacks
+ * which are executed in the context of the main thread.
+ */
+static void
+set_connected (gboolean new_connected)
+{
+  g_static_mutex_lock (&state_mutex);
+  connected = new_connected;
+  g_static_mutex_unlock (&state_mutex);
+}
+
+static void
+set_logged_in (gboolean new_logged_in)
+{
+  g_static_mutex_lock (&state_mutex);
+  logged_in = new_logged_in;
+  g_static_mutex_unlock (&state_mutex);
+}
+
+static void
+set_busy (gboolean new_busy)
+{
+  g_static_mutex_lock (&state_mutex);
+  busy = new_busy;
+  g_static_mutex_unlock (&state_mutex);
+}
+
+/* The main thread should call these functions to get the WUI thread's
+ * current state.
+ */
+gboolean
+wui_thread_is_connected (void)
+{
+  gboolean ret;
+
+  g_static_mutex_lock (&state_mutex);
+  ret = connected;
+  g_static_mutex_unlock (&state_mutex);
+  return ret;
+}
+
+gboolean
+wui_thread_is_logged_in (void)
+{
+  gboolean ret;
+
+  g_static_mutex_lock (&state_mutex);
+  ret = logged_in;
+  g_static_mutex_unlock (&state_mutex);
+  return ret;
+}
+
+gboolean
+wui_thread_is_busy (void)
+{
+  gboolean ret;
+
+  g_static_mutex_lock (&state_mutex);
+  ret = busy;
+  g_static_mutex_unlock (&state_mutex);
+  return ret;
+}
+
+/* Process a message from the main thread. */
+static gboolean
+process_message (struct message *msg)
+{
+  switch (msg->type) {
+  case QUIT:
+    if (uri) g_free (uri);
+    if (username) g_free (username);
+    if (password) g_free (password);
+    set_connected (FALSE);
+    set_logged_in (FALSE);
+    return 1;
+
+  case CONNECT:
+    if (uri) g_free (uri);
+    uri = msg->str1;
+
+    if (do_connect ()) {
+      set_connected (TRUE);
+    } else {
+      set_connected (FALSE);
+      set_logged_in (FALSE);
+    }
+    break;
+
+  case DISCONNECT:
+    /* This just forgets the state. REST is connectionless. */
+    if (uri) g_free (uri);
+    if (username) g_free (username);
+    if (password) g_free (password);
+    set_connected (FALSE);
+    set_logged_in (FALSE);
+    break;
+
+  case LOGIN:
+    if (username) g_free (username);
+    username = msg->str1;
+    if (password) g_free (password);
+    password = msg->str2;
+
+    if (connected) {
+      if (do_login ()) {
+       set_logged_in (TRUE);
+       refresh_vm_list ();
+      } else {
+       set_logged_in (FALSE);
+      }
+    }
+    break;
+
+  case REFRESH_VM_LIST:
+    refresh_vm_list ();
+    secs_between_refresh = 60;
+    break;
+
+  default:
+    DEBUG ("unknown message type %d", msg->type);
+    abort ();
+  }
+
+  return 0;
+}
+
+/* Try to connect to the current URI.  Returns true on success. */
+static gboolean
+do_connect (void)
+{
+  DEBUG ("connecting to uri %s", uri);
+  return FALSE;
+}
+
+/* Try to login to URI/login with the username and password given.
+ * Returns true on success.
+ */
+static gboolean
+do_login (void)
+{
+  DEBUG ("logging in with username %s, password *****", username);
+  return FALSE;
+}
+
+/* Refresh the list of VMs. */
+static void
+refresh_vm_list (void)
+{
+  DEBUG ("refreshing list of VMs");
+}