1 /* ovirt viewer console application
2 * Copyright (C) 2008 Red Hat Inc.
3 * Written by Richard W.M. Jones <rjones@redhat.com>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 #include <curl/curl.h>
32 /* Private functions. */
33 static gpointer wui_thread (gpointer data);
34 static void wui_thread_send_quit (void);
35 static void do_curl_init (void);
36 static gboolean do_connect (void);
37 static gboolean do_login (void);
38 static void refresh_vm_list (void);
40 /* Messages (main thread -> WUI thread only).
42 * These are passed by reference. They are allocated by the sender
43 * (ie. the main thread) and freed by the receiver (ie. the WUI thread).
46 QUIT, /* Tell the WUI thread to quit cleanly. */
47 CONNECT, /* Tell to connect (just fetch the URI). */
48 DISCONNECT, /* Tell to disconnect, forget state. */
49 LOGIN, /* Tell to login, and pass credentials. */
50 REFRESH_VM_LIST, /* Tell to refresh the VM list right away. */
54 enum message_type type;
59 /* Start the WUI thread. See main() for explanation of the threading model. */
60 static GThread *wui_gthread = NULL;
61 static GAsyncQueue *wui_thread_queue = NULL;
64 start_wui_thread (void)
68 DEBUG ("starting the WUI thread");
70 /* Create the message queue for main -> WUI thread communications. */
71 wui_thread_queue = g_async_queue_new ();
73 wui_gthread = g_thread_create (wui_thread, wui_thread_queue, TRUE, &error);
75 g_print ("%s\n", error->message);
82 stop_wui_thread (void)
84 DEBUG ("stopping the WUI thread");
87 /* Send a quit message then wait for the WUI thread to join.
89 * This "nice" shutdown could cause the UI to hang for an
90 * indefinite period (eg. if the WUI thread is engaged in some
91 * long connection or download from the remote server). But
92 * I want to keep it this way for the moment so that we can
93 * diagnose problems with the WUI thread.
95 * (This could be solved with some sort of interruptible
96 * join, but glib doesn't support that AFAICT).
98 wui_thread_send_quit ();
99 (void) g_thread_join (wui_gthread);
100 g_async_queue_unref (wui_thread_queue);
104 /* Send the quit message to the WUI thread. */
106 wui_thread_send_quit (void)
110 msg = g_new (struct message, 1);
112 g_async_queue_push (wui_thread_queue, msg);
115 /* Send the connect message to the WUI thread. */
117 wui_thread_send_connect (const char *uri)
121 msg = g_new (struct message, 1);
123 msg->str1 = g_strdup (uri);
124 g_async_queue_push (wui_thread_queue, msg);
127 /* Send the disconnect message to the WUI thread. */
129 wui_thread_send_disconnect (void)
133 msg = g_new (struct message, 1);
134 msg->type = DISCONNECT;
135 g_async_queue_push (wui_thread_queue, msg);
138 /* Send the login message to the WUI thread. */
140 wui_thread_send_login (const char *username, const char *password)
144 msg = g_new (struct message, 1);
146 msg->str1 = g_strdup (username);
147 msg->str2 = g_strdup (password);
148 g_async_queue_push (wui_thread_queue, msg);
151 /* Send the refresh VM list message to the WUI thread. */
153 wui_thread_send_refresh_vm_list (void)
157 msg = g_new (struct message, 1);
158 msg->type = REFRESH_VM_LIST;
159 g_async_queue_push (wui_thread_queue, msg);
162 /* The current state.
164 * For safety, the main thread must lock this before reading, and the
165 * WUI thread must lock this before writing. However the WUI thread
166 * does not need to lock before reading, because no other thread
169 static gboolean connected = FALSE;
170 static gboolean logged_in = FALSE;
171 static gboolean busy = FALSE;
172 static GStaticMutex state_mutex;
174 static void set_connected (gboolean);
175 static void set_logged_in (gboolean);
176 static void set_busy (gboolean);
178 /* The private state of the WUI thread. */
179 static int secs_between_refresh = 60;
180 static CURL *curl = NULL;
181 static char curl_error_buffer[CURL_ERROR_SIZE];
182 static char *uri = NULL;
183 static char *username = NULL;
184 static char *password = NULL;
186 static gboolean process_message (struct message *);
188 /* The WUI thread. See main() above for explanation of
189 * the threading model.
192 wui_thread (gpointer _queue)
194 GAsyncQueue *queue = (GAsyncQueue *) _queue;
195 gboolean quit = FALSE;
200 DEBUG ("WUI thread starting up");
202 g_async_queue_ref (queue);
204 /* In the thread's loop we check for new instructions from the main
205 * thread and carry them out. Also, if we are connected and logged
206 * in then we periodically recheck the list of VMs.
212 g_get_current_time (&tv);
213 g_time_val_add (&tv, secs_between_refresh * 1000000);
214 _msg = g_async_queue_timed_pop (queue, &tv);
216 _msg = g_async_queue_pop (queue);
220 msg = (struct message *) _msg;
223 DEBUG ("received message with msg->type = %d", msg->type);
224 quit = process_message (msg);
225 /* Don't free any strings in the message - we've saved them. */
228 /* No message, must have got a timeout instead, which means
229 * we are logged in and we should refresh the list of VMs.
230 * Note it's not an error if we temporarily lose contact
237 DEBUG ("WUI thread shutting down cleanly");
239 g_async_queue_unref (queue);
240 g_thread_exit (NULL);
241 return NULL; /* not reached? */
244 /* The WUI thread calls this to safely update the state variables.
245 * This also updates elements in the UI by setting idle callbacks
246 * which are executed in the context of the main thread.
249 set_connected (gboolean new_connected)
251 g_static_mutex_lock (&state_mutex);
252 connected = new_connected;
253 g_static_mutex_unlock (&state_mutex);
257 set_logged_in (gboolean new_logged_in)
259 g_static_mutex_lock (&state_mutex);
260 logged_in = new_logged_in;
261 g_static_mutex_unlock (&state_mutex);
265 set_busy (gboolean new_busy)
267 g_static_mutex_lock (&state_mutex);
269 g_static_mutex_unlock (&state_mutex);
272 /* The main thread should call these functions to get the WUI thread's
276 wui_thread_is_connected (void)
280 g_static_mutex_lock (&state_mutex);
282 g_static_mutex_unlock (&state_mutex);
287 wui_thread_is_logged_in (void)
291 g_static_mutex_lock (&state_mutex);
293 g_static_mutex_unlock (&state_mutex);
298 wui_thread_is_busy (void)
302 g_static_mutex_lock (&state_mutex);
304 g_static_mutex_unlock (&state_mutex);
308 /* Process a message from the main thread. */
310 process_message (struct message *msg)
314 if (curl) curl_easy_cleanup (curl);
315 if (uri) g_free (uri);
316 if (username) g_free (username);
317 if (password) g_free (password);
318 set_connected (FALSE);
319 set_logged_in (FALSE);
323 if (curl) curl_easy_cleanup (curl);
325 if (uri) g_free (uri);
329 set_connected (TRUE);
331 set_connected (FALSE);
332 set_logged_in (FALSE);
337 /* This just forgets the state. REST is connectionless. */
338 if (curl) curl_easy_cleanup (curl);
340 if (uri) g_free (uri);
342 if (username) g_free (username);
344 if (password) g_free (password);
346 set_connected (FALSE);
347 set_logged_in (FALSE);
351 if (username) g_free (username);
352 username = msg->str1;
353 if (password) g_free (password);
354 password = msg->str2;
356 /* If we're not connected, this message just updates the
357 * username and password. Otherwise if we are connected,
358 * try to login and grab the initial list of VMs.
362 set_logged_in (TRUE);
365 set_logged_in (FALSE);
370 case REFRESH_VM_LIST:
371 if (connected && logged_in) {
373 secs_between_refresh = 60;
378 DEBUG ("unknown message type %d", msg->type);
385 /* Macro for easy handling of CURL errors. */
386 #define CURL_CHECK_ERROR(fn, args) \
388 CURLcode __r = fn args; \
389 if (__r != CURLE_OK) { \
390 fprintf (stderr, "%s: %s\n", #fn, curl_easy_strerror (__r)); \
396 write_fn (void *ptr, size_t size, size_t nmemb, void *stream)
398 int bytes = size * nmemb;
403 header_fn (void *ptr, size_t size, size_t nmemb, void *stream)
405 int bytes = size * nmemb;
409 /* Called from the message loop to initialize the CURL handle. */
413 DEBUG ("initializing libcurl");
415 curl = curl_easy_init ();
416 if (!curl) { /* This is probably quite bad, so abort. */
417 DEBUG ("curl_easy_init failed");
421 CURL_CHECK_ERROR (curl_easy_setopt,
422 (curl, CURLOPT_CAINFO, cainfo));
423 CURL_CHECK_ERROR (curl_easy_setopt,
424 (curl, CURLOPT_SSL_VERIFYHOST, check_cert ? 2 : 0));
425 CURL_CHECK_ERROR (curl_easy_setopt,
426 (curl, CURLOPT_SSL_VERIFYPEER, check_cert ? 1 : 0));
428 CURL_CHECK_ERROR (curl_easy_setopt,
429 (curl, CURLOPT_WRITEFUNCTION, write_fn));
430 CURL_CHECK_ERROR (curl_easy_setopt,
431 (curl, CURLOPT_HEADERFUNCTION, header_fn));
433 /* This enables error messages in curl_easy_perform. */
434 CURL_CHECK_ERROR (curl_easy_setopt,
435 (curl, CURLOPT_ERRORBUFFER, curl_error_buffer));
437 /* This enables cookie handling, using an internal cookiejar. */
438 CURL_CHECK_ERROR (curl_easy_setopt,
439 (curl, CURLOPT_COOKIEFILE, ""));
442 /* Called from the message loop. Try to connect to the current URI.
443 * Returns true on success.
450 DEBUG ("connecting to uri %s", uri);
452 /* Set the URI for libcurl. */
453 CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, uri));
455 /* Try to fetch the URI. */
456 if (CURL_CHECK_ERROR (curl_easy_perform, (curl)) != CURLE_OK)
459 CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code));
460 DEBUG ("HTTP return code is %ld", code);
461 if (code != 200 || code != 302 || code != 401)
467 /* Called from the message loop. Try to login to 'URI/login' with the
468 * current username and password. Returns true on success.
473 DEBUG ("logging in with username %s, password *****", username);
477 /* Called from the message loop. Refresh the list of VMs. */
479 refresh_vm_list (void)
481 DEBUG ("refreshing list of VMs");