Push daily update.
[ovirt-viewer.git] / wui_thread.c
1 /* ovirt viewer console application
2  * Copyright (C) 2008 Red Hat Inc.
3  * Written by Richard W.M. Jones <rjones@redhat.com>
4  *
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.
9  *
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.
14  *
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.
18  */
19
20 #include <config.h>
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <assert.h>
25
26 #include <glib.h>
27
28 #include <curl/curl.h>
29
30 #include "internal.h"
31
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);
39
40 /* Messages (main thread -> WUI thread only).
41  *
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).
44  */
45 enum message_type {
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. */
51 };
52
53 struct message {
54   enum message_type type;
55   char *str1;
56   char *str2;
57 };
58
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;
62
63 void
64 start_wui_thread (void)
65 {
66   GError *error = NULL;
67
68   DEBUG ("starting the WUI thread");
69
70   /* Create the message queue for main -> WUI thread communications. */
71   wui_thread_queue = g_async_queue_new ();
72
73   wui_gthread = g_thread_create (wui_thread, wui_thread_queue, TRUE, &error);
74   if (error) {
75     g_print ("%s\n", error->message);
76     g_error_free (error);
77     exit (1);
78   }
79 }
80
81 void
82 stop_wui_thread (void)
83 {
84   DEBUG ("stopping the WUI thread");
85   assert (wui_gthread);
86
87   /* Send a quit message then wait for the WUI thread to join.
88    *
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.
94    *
95    * (This could be solved with some sort of interruptible
96    * join, but glib doesn't support that AFAICT).
97    */
98   wui_thread_send_quit ();
99   (void) g_thread_join (wui_gthread);
100   g_async_queue_unref (wui_thread_queue);
101   wui_gthread = NULL;
102 }
103
104 /* Send the quit message to the WUI thread. */
105 static void
106 wui_thread_send_quit (void)
107 {
108   struct message *msg;
109
110   msg = g_new (struct message, 1);
111   msg->type = QUIT;
112   g_async_queue_push (wui_thread_queue, msg);
113 }
114
115 /* Send the connect message to the WUI thread. */
116 void
117 wui_thread_send_connect (const char *uri)
118 {
119   struct message *msg;
120
121   msg = g_new (struct message, 1);
122   msg->type = CONNECT;
123   msg->str1 = g_strdup (uri);
124   g_async_queue_push (wui_thread_queue, msg);
125 }
126
127 /* Send the disconnect message to the WUI thread. */
128 void
129 wui_thread_send_disconnect (void)
130 {
131   struct message *msg;
132
133   msg = g_new (struct message, 1);
134   msg->type = DISCONNECT;
135   g_async_queue_push (wui_thread_queue, msg);
136 }
137
138 /* Send the login message to the WUI thread. */
139 void
140 wui_thread_send_login (const char *username, const char *password)
141 {
142   struct message *msg;
143
144   msg = g_new (struct message, 1);
145   msg->type = LOGIN;
146   msg->str1 = g_strdup (username);
147   msg->str2 = g_strdup (password);
148   g_async_queue_push (wui_thread_queue, msg);
149 }
150
151 /* Send the refresh VM list message to the WUI thread. */
152 void
153 wui_thread_send_refresh_vm_list (void)
154 {
155   struct message *msg;
156
157   msg = g_new (struct message, 1);
158   msg->type = REFRESH_VM_LIST;
159   g_async_queue_push (wui_thread_queue, msg);
160 }
161
162 /* The current state.
163  *
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
167  * can modify it.
168  */
169 static gboolean connected = FALSE;
170 static gboolean logged_in = FALSE;
171 static gboolean busy = FALSE;
172 static GStaticMutex state_mutex;
173
174 static void set_connected (gboolean);
175 static void set_logged_in (gboolean);
176 static void set_busy (gboolean);
177
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;
185
186 static gboolean process_message (struct message *);
187
188 /* The WUI thread.  See main() above for explanation of
189  * the threading model.
190  */
191 static gpointer
192 wui_thread (gpointer _queue)
193 {
194   GAsyncQueue *queue = (GAsyncQueue *) _queue;
195   gboolean quit = FALSE;
196   GTimeVal tv;
197   gpointer _msg;
198   struct message *msg;
199
200   DEBUG ("WUI thread starting up");
201
202   g_async_queue_ref (queue);
203
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.
207    */
208   while (!quit) {
209     set_busy (FALSE);
210
211     if (logged_in) {
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);
215     } else
216       _msg = g_async_queue_pop (queue);
217
218     set_busy (TRUE);
219
220     msg = (struct message *) _msg;
221
222     if (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. */
226       g_free (msg);
227     } else {
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
231        * with the WUI.
232        */
233       refresh_vm_list ();
234     }
235   }
236
237   DEBUG ("WUI thread shutting down cleanly");
238
239   g_async_queue_unref (queue);
240   g_thread_exit (NULL);
241   return NULL; /* not reached? */
242 }
243
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.
247  */
248 static void
249 set_connected (gboolean new_connected)
250 {
251   g_static_mutex_lock (&state_mutex);
252   connected = new_connected;
253   g_static_mutex_unlock (&state_mutex);
254 }
255
256 static void
257 set_logged_in (gboolean new_logged_in)
258 {
259   g_static_mutex_lock (&state_mutex);
260   logged_in = new_logged_in;
261   g_static_mutex_unlock (&state_mutex);
262 }
263
264 static void
265 set_busy (gboolean new_busy)
266 {
267   g_static_mutex_lock (&state_mutex);
268   busy = new_busy;
269   g_static_mutex_unlock (&state_mutex);
270 }
271
272 /* The main thread should call these functions to get the WUI thread's
273  * current state.
274  */
275 gboolean
276 wui_thread_is_connected (void)
277 {
278   gboolean ret;
279
280   g_static_mutex_lock (&state_mutex);
281   ret = connected;
282   g_static_mutex_unlock (&state_mutex);
283   return ret;
284 }
285
286 gboolean
287 wui_thread_is_logged_in (void)
288 {
289   gboolean ret;
290
291   g_static_mutex_lock (&state_mutex);
292   ret = logged_in;
293   g_static_mutex_unlock (&state_mutex);
294   return ret;
295 }
296
297 gboolean
298 wui_thread_is_busy (void)
299 {
300   gboolean ret;
301
302   g_static_mutex_lock (&state_mutex);
303   ret = busy;
304   g_static_mutex_unlock (&state_mutex);
305   return ret;
306 }
307
308 /* Process a message from the main thread. */
309 static gboolean
310 process_message (struct message *msg)
311 {
312   switch (msg->type) {
313   case QUIT:
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);
320     return 1;
321
322   case CONNECT:
323     if (curl) curl_easy_cleanup (curl);
324     do_curl_init ();
325     if (uri) g_free (uri);
326     uri = msg->str1;
327
328     if (do_connect ()) {
329       set_connected (TRUE);
330     } else {
331       set_connected (FALSE);
332       set_logged_in (FALSE);
333     }
334     break;
335
336   case DISCONNECT:
337     /* This just forgets the state. REST is connectionless. */
338     if (curl) curl_easy_cleanup (curl);
339     curl = NULL;
340     if (uri) g_free (uri);
341     uri = NULL;
342     if (username) g_free (username);
343     username = NULL;
344     if (password) g_free (password);
345     password = NULL;
346     set_connected (FALSE);
347     set_logged_in (FALSE);
348     break;
349
350   case LOGIN:
351     if (username) g_free (username);
352     username = msg->str1;
353     if (password) g_free (password);
354     password = msg->str2;
355
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.
359      */
360     if (connected) {
361       if (do_login ()) {
362         set_logged_in (TRUE);
363         refresh_vm_list ();
364       } else {
365         set_logged_in (FALSE);
366       }
367     }
368     break;
369
370   case REFRESH_VM_LIST:
371     if (connected && logged_in) {
372       refresh_vm_list ();
373       secs_between_refresh = 60;
374     }
375     break;
376
377   default:
378     DEBUG ("unknown message type %d", msg->type);
379     abort ();
380   }
381
382   return 0;
383 }
384
385 /* Macro for easy handling of CURL errors. */
386 #define CURL_CHECK_ERROR(fn, args)                                      \
387   ({                                                                    \
388     CURLcode __r = fn args;                                             \
389     if (__r != CURLE_OK) {                                              \
390       fprintf (stderr, "%s: %s\n", #fn, curl_easy_strerror (__r));      \
391     }                                                                   \
392     __r;                                                                \
393   })
394
395 static size_t
396 write_fn (void *ptr, size_t size, size_t nmemb, void *stream)
397 {
398   int bytes = size * nmemb;
399   return bytes;
400 }
401
402 static size_t
403 header_fn (void *ptr, size_t size, size_t nmemb, void *stream)
404 {
405   int bytes = size * nmemb;
406   return bytes;
407 }
408
409 /* Called from the message loop to initialize the CURL handle. */
410 static void
411 do_curl_init (void)
412 {
413   DEBUG ("initializing libcurl");
414
415   curl = curl_easy_init ();
416   if (!curl) {             /* This is probably quite bad, so abort. */
417     DEBUG ("curl_easy_init failed");
418     abort ();
419   }
420
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));
427
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));
432
433   /* This enables error messages in curl_easy_perform. */
434   CURL_CHECK_ERROR (curl_easy_setopt,
435                     (curl, CURLOPT_ERRORBUFFER, curl_error_buffer));
436
437   /* This enables cookie handling, using an internal cookiejar. */
438   CURL_CHECK_ERROR (curl_easy_setopt,
439                     (curl, CURLOPT_COOKIEFILE, ""));
440 }
441
442 /* Called from the message loop.  Try to connect to the current URI.
443  * Returns true on success.
444  */
445 static gboolean
446 do_connect (void)
447 {
448   long code = 0;
449
450   DEBUG ("connecting to uri %s", uri);
451
452   /* Set the URI for libcurl. */
453   CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, uri));
454
455   /* Try to fetch the URI. */
456   if (CURL_CHECK_ERROR (curl_easy_perform, (curl)) != CURLE_OK)
457     return FALSE;
458
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)
462     return FALSE;
463
464   return TRUE;
465 }
466
467 /* Called from the message loop.  Try to login to 'URI/login' with the
468  * current username and password.  Returns true on success.
469  */
470 static gboolean
471 do_login (void)
472 {
473   DEBUG ("logging in with username %s, password *****", username);
474   return FALSE;
475 }
476
477 /* Called from the message loop.  Refresh the list of VMs. */
478 static void
479 refresh_vm_list (void)
480 {
481   DEBUG ("refreshing list of VMs");
482 }