1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /* command-queue.c
3  *
4  * Copyright (C) 2010  Sébastien Granjoux
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 
26 #include "command-queue.h"
27 #include <libanjuta/anjuta-debug.h>
28 #include <libanjuta/interfaces/ianjuta-project-backend.h>
29 
30 #include <string.h>
31 
32 /*
33 
34 2 Threads: GUI and Work
35 
36 Add new source.
37 
38 1. Work add new node
39  + Obvious, because it has the parent node
40  - Not possible, if the GUI has to get all child of the same node
41 
42 2. Work add new node, GUI use tree data
43 
44 3. Work create new node only but doesn't add it
45 
46 4. GUI create a new node and call the save function later
47 
48 
49 The GUI cannot change links, else the Work cannot follow links when getting a
50 node. The Work can even need parent links.
51 => GUI cannot read links except in particular case
52 
53 The GUI can read and write common part
54 => We need to copy the properties anyway when changing them
55 
56 The GUI can only read common part
57 =>
58 
59 Have a proxy node for setting properties, this proxy will copy add common data
60 and keep a link with the original node and reference count. The GUI can still
61 read all data in the proxy without disturbing the Work which can change the
62 underlines node. The proxy address is returned immediatly when the
63 set_properties function is used. If another set_properties is done on the same
64 node the proxy is reused, incrementing reference count.
65 
66 There is no need to proxy after add or remove functions, because the links are
67 already copied in the module.
68 
69 After changing a property should we reload automatically ?
70 
71 
72 Reloading a node can change its property, so we need a proxy for the load too.
73 
74 Proxy has to be created in GUI, because we need to update tree data.
75 
76 Instead of using a Proxy, we can copy all data everytime, this will allow
77 automatic reload.
78 
79 
80 
81  Work has always a full access to link
82  GUI read a special GNode tree created by thread
83 
84  Work can always read common data, and can write them before sending them to GUI
85  or in when modification are requested by the GUI (the GUI get a proxy)
86  GUI can only read common data
87 
88  Work has always a full access to specific data.
89  GUI has no access to specific data
90 
91 
92 */
93 
94 /* Types
95  *---------------------------------------------------------------------------*/
96 
97 struct _PmCommandQueue
98 {
99 	GQueue *job_queue;
100 	GAsyncQueue *work_queue;
101 	GAsyncQueue *done_queue;
102 	GThread *worker;
103 	guint idle_func;
104 	gboolean stopping;
105 	guint busy;
106 };
107 
108 
109 /* Signal
110  *---------------------------------------------------------------------------*/
111 
112 /* Command functions
113  *---------------------------------------------------------------------------*/
114 
115 static gboolean
pm_command_exit_work(PmJob * job)116 pm_command_exit_work (PmJob *job)
117 {
118 	PmCommandQueue *queue;
119 
120 	g_return_val_if_fail (job != NULL, FALSE);
121 
122 	queue = (PmCommandQueue *)job->user_data;
123 
124 	/* Push job in complete queue as g_thread_exit will stop the thread
125 	 * immediatly */
126 	g_async_queue_push (queue->done_queue, job);
127 	g_thread_exit (0);
128 
129 	return TRUE;
130 }
131 
132 static PmCommandWork PmExitCommand = {NULL, pm_command_exit_work, NULL};
133 
134 /* Forward declarations
135  *---------------------------------------------------------------------------*/
136 
137 static gboolean pm_command_queue_idle (PmCommandQueue *queue);
138 
139 /* Worker thread functions
140  *---------------------------------------------------------------------------*/
141 
142 /* Run work function in worker thread */
143 static gpointer
pm_command_queue_thread_main_loop(PmCommandQueue * queue)144 pm_command_queue_thread_main_loop (PmCommandQueue *queue)
145 {
146 	for (;;)
147 	{
148 		PmJob *job;
149 		PmCommandFunc func;
150 
151 		/* Get new job */
152 		job = (PmJob *)g_async_queue_pop (queue->work_queue);
153 
154 		/* Get work function and call it if possible */
155 		func = job->work->worker;
156 		if (func != NULL)
157 		{
158 			func (job);
159 		}
160 
161 		/* Push completed job in queue */
162 		g_async_queue_push (queue->done_queue, job);
163 	}
164 
165 	return NULL;
166 }
167 
168 /* Main thread functions
169  *---------------------------------------------------------------------------*/
170 
171 static gboolean
pm_command_queue_start_thread(PmCommandQueue * queue)172 pm_command_queue_start_thread (PmCommandQueue *queue)
173 {
174 	queue->done_queue = g_async_queue_new ();
175 	queue->work_queue = g_async_queue_new ();
176 	queue->job_queue = g_queue_new ();
177 
178 	queue->worker = g_thread_new ("am-project-worker",
179 	                              (GThreadFunc) pm_command_queue_thread_main_loop, queue);
180 
181 	if (queue->worker == NULL) {
182 		g_async_queue_unref (queue->work_queue);
183 		queue->work_queue = NULL;
184 		g_async_queue_unref (queue->done_queue);
185 		queue->done_queue = NULL;
186 		g_queue_free (queue->job_queue);
187 		queue->job_queue = NULL;
188 
189 		return FALSE;
190 	}
191 	else
192 	{
193 		queue->stopping = FALSE;
194 		queue->idle_func = g_idle_add ((GSourceFunc) pm_command_queue_idle, queue);
195 
196 		return TRUE;
197 	}
198 }
199 
200 static gboolean
pm_command_queue_stop_thread(PmCommandQueue * queue)201 pm_command_queue_stop_thread (PmCommandQueue *queue)
202 {
203 	if (queue->job_queue)
204 	{
205 		PmJob *job;
206 
207 		// Remove idle function
208 		queue->stopping = TRUE;
209 		queue->idle_func = 0;
210 
211 		// Request to terminate thread
212 		job = pm_job_new (&PmExitCommand, NULL, NULL, NULL, 0, NULL, NULL, queue);
213 		g_async_queue_push (queue->work_queue, job);
214 		g_thread_join (queue->worker);
215 		queue->worker = NULL;
216 
217 		// Free queue
218 		g_async_queue_unref (queue->work_queue);
219 		queue->work_queue = NULL;
220 		g_queue_foreach (queue->job_queue, (GFunc)pm_job_free, NULL);
221 		g_queue_free (queue->job_queue);
222 		queue->job_queue = NULL;
223 		for (;;)
224 		{
225 			job = g_async_queue_try_pop (queue->done_queue);
226 			if (job == NULL) break;
227 			pm_job_free (job);
228 		}
229 		queue->done_queue = NULL;
230 	}
231 
232 	return TRUE;
233 }
234 
235 /* Run a command in job queue */
236 static gboolean
pm_command_queue_run_command(PmCommandQueue * queue)237 pm_command_queue_run_command (PmCommandQueue *queue)
238 {
239 	gboolean running = TRUE;
240 
241 	if (queue->busy == 0)
242 	{
243 		/* Worker thread is waiting for new command, check job queue */
244 		PmJob *job;
245 
246 		do
247 		{
248 			PmCommandFunc func;
249 
250 			/* Get next command */
251 			job = g_queue_pop_head (queue->job_queue);
252 			running = job != NULL;
253 			if (!running) break;
254 
255 			/* Get setup function and call it if possible */
256 			func = job->work->setup;
257 			if (func != NULL)
258 			{
259 				running = func (job);
260 			}
261 
262 			if (running)
263 			{
264 				/* Execute work function in the worker thread */
265 				queue->busy = 1;
266 
267 				if (queue->idle_func == 0)
268 				{
269 					queue->idle_func = g_idle_add ((GSourceFunc) pm_command_queue_idle, queue);
270 				}
271 				g_async_queue_push (queue->work_queue, job);
272 			}
273 			else
274 			{
275 				/* Discard command */
276 				pm_job_free (job);
277 			}
278 		} while (!running);
279 	}
280 
281 	return running;
282 }
283 
284 static gboolean
pm_command_queue_idle(PmCommandQueue * queue)285 pm_command_queue_idle (PmCommandQueue *queue)
286 {
287 	gboolean running;
288 
289 	for (;;)
290 	{
291 		PmCommandFunc func;
292 		PmJob *job;
293 
294 		/* Remove idle handler if queue is destroyed */
295 		if (queue->stopping) return FALSE;
296 
297 		/* Get completed command */
298 		job = (PmJob *)g_async_queue_try_pop (queue->done_queue);
299 		if (job == NULL) break;
300 
301 		/* Get complete function and call it if possible */
302 		func = job->work->complete;
303 		if (func != NULL)
304 		{
305 			running = func (job);
306 		}
307 		pm_job_free (job);
308 		queue->busy--;
309 	}
310 
311 	running = pm_command_queue_run_command (queue);
312 	if (!running) queue->idle_func = 0;
313 
314 	return running;
315 }
316 
317 /* Job functions
318  *---------------------------------------------------------------------------*/
319 
320 PmJob *
pm_job_new(PmCommandWork * work,AnjutaProjectNode * node,AnjutaProjectNode * parent,AnjutaProjectNode * sibling,AnjutaProjectNodeType type,GFile * file,const gchar * name,gpointer user_data)321 pm_job_new (PmCommandWork* work, AnjutaProjectNode *node, AnjutaProjectNode *parent, AnjutaProjectNode *sibling, AnjutaProjectNodeType type, GFile *file, const gchar *name, gpointer user_data)
322 {
323 	PmJob *job;
324 
325 	job = g_new0 (PmJob, 1);
326 	job->work = work;
327 	if (node != NULL) job->node = g_object_ref (node);
328 	if (parent != NULL) job->parent = g_object_ref (parent);
329 	if (sibling != NULL) job->sibling = g_object_ref (sibling);
330 	job->type = type;
331 	if (file != NULL) job->file = g_object_ref (file);
332 	if (name != NULL) job->name = g_strdup (name);
333 	job->user_data = user_data;
334 
335 	return job;
336 }
337 
338 void
pm_job_free(PmJob * job)339 pm_job_free (PmJob *job)
340 {
341 	if (job->error != NULL) g_error_free (job->error);
342 	if (job->map != NULL) g_hash_table_destroy (job->map);
343 	if (job->file != NULL) g_object_unref (job->file);
344 	if (job->name != NULL) g_free (job->name);
345 	if (job->sibling != NULL) g_object_unref (job->sibling);
346 	if (job->parent != NULL) g_object_unref (job->parent);
347 	if (job->node != NULL) g_object_unref (job->node);
348 }
349 
350 void
pm_job_set_parent(PmJob * job,AnjutaProjectNode * parent)351 pm_job_set_parent (PmJob *job, AnjutaProjectNode *parent)
352 {
353 	if (job->parent != parent)
354 	{
355 		if (job->parent != NULL) g_object_unref (job->parent);
356 		if (parent != NULL) g_object_ref (parent);
357 		job->parent = parent;
358 	}
359 }
360 
361 /* Public functions
362  *---------------------------------------------------------------------------*/
363 
364 void
pm_command_queue_push(PmCommandQueue * queue,PmJob * job)365 pm_command_queue_push (PmCommandQueue *queue, PmJob *job)
366 {
367 	g_queue_push_tail (queue->job_queue, job);
368 
369 	pm_command_queue_run_command (queue);
370 }
371 
372 gboolean
pm_command_queue_is_busy(PmCommandQueue * queue)373 pm_command_queue_is_busy (PmCommandQueue *queue)
374 {
375 	//g_message ("pm_command_queue_is_empty %d %d %d busy %d", g_queue_get_length (queue->job_queue), g_async_queue_length(queue->work_queue), g_async_queue_length(queue->done_queue), queue->busy);
376 	return queue->busy;
377 }
378 
379 PmCommandQueue*
pm_command_queue_new(void)380 pm_command_queue_new (void)
381 {
382 	PmCommandQueue *queue;
383 
384 	queue = g_new0 (PmCommandQueue, 1);
385 
386 	queue->job_queue = NULL;
387 	queue->work_queue = NULL;
388 	queue->done_queue = NULL;
389 	queue->worker = NULL;
390 	queue->idle_func = 0;
391 	queue->stopping = FALSE;
392 	queue->busy = 0;
393 
394 	pm_command_queue_start_thread (queue);
395 
396 	return queue;
397 }
398 
399 static gboolean
pm_command_queue_delayed_free(gpointer data)400 pm_command_queue_delayed_free (gpointer data)
401 {
402 	g_free (data);
403 
404 	return FALSE;
405 }
406 
407 void
pm_command_queue_free(PmCommandQueue * queue)408 pm_command_queue_free (PmCommandQueue *queue)
409 {
410 	pm_command_queue_stop_thread (queue);
411 
412 	g_idle_add (pm_command_queue_delayed_free, queue);
413 }
414 
415