/* ovirt viewer console application * Copyright (C) 2008 Red Hat Inc. * Written by Richard W.M. Jones * * 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 #include #include #include #include #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"); }