Restructure main program and separate thread for WUI interaction.
[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 "internal.h"
29
30 /* Private functions. */
31 static gpointer wui_thread (gpointer data);
32 static void wui_thread_send_quit (void);
33 static gboolean do_connect (void);
34 static gboolean do_login (void);
35 static void refresh_vm_list (void);
36
37 /* Messages (main thread -> WUI thread only).
38  *
39  * These are passed by reference.  They are allocated by the sender
40  * (ie. the main thread) and freed by the receiver (ie. the WUI thread).
41  */
42 enum message_type {
43   QUIT,                    /* Tell the WUI thread to quit cleanly. */
44   CONNECT,                 /* Tell to connect (just fetch the URI). */
45   DISCONNECT,              /* Tell to disconnect, forget state. */
46   LOGIN,                   /* Tell to login, and pass credentials. */
47   REFRESH_VM_LIST,         /* Tell to refresh the VM list right away. */
48 };
49
50 struct message {
51   enum message_type type;
52   char *str1;
53   char *str2;
54 };
55
56 /* Start the WUI thread.  See main() for explanation of the threading model. */
57 static GThread *wui_gthread = NULL;
58 static GAsyncQueue *wui_thread_queue = NULL;
59
60 void
61 start_wui_thread (void)
62 {
63   GError *error = NULL;
64
65   DEBUG ("starting the WUI thread");
66
67   /* Create the message queue for main -> WUI thread communications. */
68   wui_thread_queue = g_async_queue_new ();
69
70   wui_gthread = g_thread_create (wui_thread, wui_thread_queue, TRUE, &error);
71   if (error) {
72     g_print ("%s\n", error->message);
73     g_error_free (error);
74     exit (1);
75   }
76 }
77
78 void
79 stop_wui_thread (void)
80 {
81   DEBUG ("stopping the WUI thread");
82   assert (wui_gthread);
83
84   /* Send a quit message then wait for the WUI thread to join.
85    *
86    * This "nice" shutdown could cause the UI to hang for an
87    * indefinite period (eg. if the WUI thread is engaged in some
88    * long connection or download from the remote server).  But
89    * I want to keep it this way for the moment so that we can
90    * diagnose problems with the WUI thread.
91    *
92    * (This could be solved with some sort of interruptible
93    * join, but glib doesn't support that AFAICT).
94    */
95   wui_thread_send_quit ();
96   (void) g_thread_join (wui_gthread);
97   g_async_queue_unref (wui_thread_queue);
98   wui_gthread = NULL;
99 }
100
101 /* Send the quit message to the WUI thread. */
102 static void
103 wui_thread_send_quit (void)
104 {
105   struct message *msg;
106
107   msg = g_new (struct message, 1);
108   msg->type = QUIT;
109   g_async_queue_push (wui_thread_queue, msg);
110 }
111
112 /* Send the connect message to the WUI thread. */
113 void
114 wui_thread_send_connect (const char *uri)
115 {
116   struct message *msg;
117
118   msg = g_new (struct message, 1);
119   msg->type = CONNECT;
120   msg->str1 = g_strdup (uri);
121   g_async_queue_push (wui_thread_queue, msg);
122 }
123
124 /* Send the disconnect message to the WUI thread. */
125 void
126 wui_thread_send_disconnect (void)
127 {
128   struct message *msg;
129
130   msg = g_new (struct message, 1);
131   msg->type = DISCONNECT;
132   g_async_queue_push (wui_thread_queue, msg);
133 }
134
135 /* Send the login message to the WUI thread. */
136 void
137 wui_thread_send_login (const char *username, const char *password)
138 {
139   struct message *msg;
140
141   msg = g_new (struct message, 1);
142   msg->type = LOGIN;
143   msg->str1 = g_strdup (username);
144   msg->str2 = g_strdup (password);
145   g_async_queue_push (wui_thread_queue, msg);
146 }
147
148 /* Send the refresh VM list message to the WUI thread. */
149 void
150 wui_thread_send_refresh_vm_list (void)
151 {
152   struct message *msg;
153
154   msg = g_new (struct message, 1);
155   msg->type = REFRESH_VM_LIST;
156   g_async_queue_push (wui_thread_queue, msg);
157 }
158
159 /* The current state.
160  *
161  * For safety, the main thread must lock this before reading, and the
162  * WUI thread must lock this before writing.  However the WUI thread
163  * does not need to lock before reading, because no other thread
164  * can modify it.
165  */
166 static gboolean connected = FALSE;
167 static gboolean logged_in = FALSE;
168 static gboolean busy = FALSE;
169 static GStaticMutex state_mutex;
170
171 static void set_connected (gboolean);
172 static void set_logged_in (gboolean);
173 static void set_busy (gboolean);
174
175 /* The private state of the WUI thread. */
176 static int secs_between_refresh = 60;
177 static char *uri = NULL;
178 static char *username = NULL;
179 static char *password = NULL;
180
181 static gboolean process_message (struct message *);
182
183 /* The WUI thread.  See main() above for explanation of
184  * the threading model.
185  */
186 static gpointer
187 wui_thread (gpointer _queue)
188 {
189   GAsyncQueue *queue = (GAsyncQueue *) _queue;
190   gboolean quit = FALSE;
191   GTimeVal tv;
192   gpointer _msg;
193   struct message *msg;
194
195   DEBUG ("WUI thread starting up");
196
197   g_async_queue_ref (queue);
198
199   /* In the thread's loop we check for new instructions from the main
200    * thread and carry them out.  Also, if we are connected and logged
201    * in then we periodically recheck the list of VMs.
202    */
203   while (!quit) {
204     set_busy (FALSE);
205
206     if (logged_in) {
207       g_get_current_time (&tv);
208       g_time_val_add (&tv, secs_between_refresh * 1000000);
209       _msg = g_async_queue_timed_pop (queue, &tv);
210     } else
211       _msg = g_async_queue_pop (queue);
212
213     set_busy (TRUE);
214
215     msg = (struct message *) _msg;
216
217     if (msg) {
218       DEBUG ("received message %d", msg->type);
219       quit = process_message (msg);
220       /* Don't free any strings in the message - we've saved them. */
221       g_free (msg);
222     } else {
223       /* No message, must have got a timeout instead, which means
224        * we are logged in and we should refresh the list of VMs.
225        * Note it's not an error if we temporarily lose contact
226        * with the WUI.
227        */
228       refresh_vm_list ();
229     }
230   }
231
232   DEBUG ("WUI thread shutting down cleanly");
233
234   g_async_queue_unref (queue);
235   g_thread_exit (NULL);
236   return NULL; /* not reached? */
237 }
238
239 /* The WUI thread calls this to safely update the state variables.
240  * This also updates elements in the UI by setting idle callbacks
241  * which are executed in the context of the main thread.
242  */
243 static void
244 set_connected (gboolean new_connected)
245 {
246   g_static_mutex_lock (&state_mutex);
247   connected = new_connected;
248   g_static_mutex_unlock (&state_mutex);
249 }
250
251 static void
252 set_logged_in (gboolean new_logged_in)
253 {
254   g_static_mutex_lock (&state_mutex);
255   logged_in = new_logged_in;
256   g_static_mutex_unlock (&state_mutex);
257 }
258
259 static void
260 set_busy (gboolean new_busy)
261 {
262   g_static_mutex_lock (&state_mutex);
263   busy = new_busy;
264   g_static_mutex_unlock (&state_mutex);
265 }
266
267 /* The main thread should call these functions to get the WUI thread's
268  * current state.
269  */
270 gboolean
271 wui_thread_is_connected (void)
272 {
273   gboolean ret;
274
275   g_static_mutex_lock (&state_mutex);
276   ret = connected;
277   g_static_mutex_unlock (&state_mutex);
278   return ret;
279 }
280
281 gboolean
282 wui_thread_is_logged_in (void)
283 {
284   gboolean ret;
285
286   g_static_mutex_lock (&state_mutex);
287   ret = logged_in;
288   g_static_mutex_unlock (&state_mutex);
289   return ret;
290 }
291
292 gboolean
293 wui_thread_is_busy (void)
294 {
295   gboolean ret;
296
297   g_static_mutex_lock (&state_mutex);
298   ret = busy;
299   g_static_mutex_unlock (&state_mutex);
300   return ret;
301 }
302
303 /* Process a message from the main thread. */
304 static gboolean
305 process_message (struct message *msg)
306 {
307   switch (msg->type) {
308   case QUIT:
309     if (uri) g_free (uri);
310     if (username) g_free (username);
311     if (password) g_free (password);
312     set_connected (FALSE);
313     set_logged_in (FALSE);
314     return 1;
315
316   case CONNECT:
317     if (uri) g_free (uri);
318     uri = msg->str1;
319
320     if (do_connect ()) {
321       set_connected (TRUE);
322     } else {
323       set_connected (FALSE);
324       set_logged_in (FALSE);
325     }
326     break;
327
328   case DISCONNECT:
329     /* This just forgets the state. REST is connectionless. */
330     if (uri) g_free (uri);
331     if (username) g_free (username);
332     if (password) g_free (password);
333     set_connected (FALSE);
334     set_logged_in (FALSE);
335     break;
336
337   case LOGIN:
338     if (username) g_free (username);
339     username = msg->str1;
340     if (password) g_free (password);
341     password = msg->str2;
342
343     if (connected) {
344       if (do_login ()) {
345         set_logged_in (TRUE);
346         refresh_vm_list ();
347       } else {
348         set_logged_in (FALSE);
349       }
350     }
351     break;
352
353   case REFRESH_VM_LIST:
354     refresh_vm_list ();
355     secs_between_refresh = 60;
356     break;
357
358   default:
359     DEBUG ("unknown message type %d", msg->type);
360     abort ();
361   }
362
363   return 0;
364 }
365
366 /* Try to connect to the current URI.  Returns true on success. */
367 static gboolean
368 do_connect (void)
369 {
370   DEBUG ("connecting to uri %s", uri);
371   return FALSE;
372 }
373
374 /* Try to login to URI/login with the username and password given.
375  * Returns true on success.
376  */
377 static gboolean
378 do_login (void)
379 {
380   DEBUG ("logging in with username %s, password *****", username);
381   return FALSE;
382 }
383
384 /* Refresh the list of VMs. */
385 static void
386 refresh_vm_list (void)
387 {
388   DEBUG ("refreshing list of VMs");
389 }