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