1 /*
2  * Copyright (C) 2009 - 2012 Vivien Malerba <malerba@gnome-db.org>
3  * Copyright (C) 2010 David King <davidk@openismus.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  */
19 
20 #include <string.h>
21 #include <glib/gi18n-lib.h>
22 #include "browser-virtual-connection.h"
23 #include "browser-connection-priv.h"
24 #include <virtual/libgda-virtual.h>
25 #include "../tool-utils.h"
26 
27 /*
28  * Main static functions
29  */
30 static void browser_virtual_connection_class_init (BrowserVirtualConnectionClass *klass);
31 static void browser_virtual_connection_init (BrowserVirtualConnection *stmt);
32 static void browser_virtual_connection_dispose (GObject *object);
33 static void browser_virtual_connection_set_property (GObject *object,
34 						     guint param_id,
35 						     const GValue *value,
36 						     GParamSpec *pspec);
37 static void browser_virtual_connection_get_property (GObject *object,
38 						     guint param_id,
39 						     GValue *value,
40 						     GParamSpec *pspec);
41 
42 struct _BrowserVirtualConnectionPrivate
43 {
44 	GtkTable    *layout_table;
45 	BrowserVirtualConnectionSpecs *specs;
46 };
47 
48 /* get a pointer to the parents to be able to call their destructor */
49 static GObjectClass  *parent_class = NULL;
50 
51 /* properties */
52 enum {
53         PROP_0,
54         PROP_SPECS
55 };
56 
57 GType
browser_virtual_connection_get_type(void)58 browser_virtual_connection_get_type (void)
59 {
60 	static GType type = 0;
61 
62 	if (G_UNLIKELY (type == 0)) {
63 		static GMutex registering;
64 		static const GTypeInfo info = {
65 			sizeof (BrowserVirtualConnectionClass),
66 			(GBaseInitFunc) NULL,
67 			(GBaseFinalizeFunc) NULL,
68 			(GClassInitFunc) browser_virtual_connection_class_init,
69 			NULL,
70 			NULL,
71 			sizeof (BrowserVirtualConnection),
72 			0,
73 			(GInstanceInitFunc) browser_virtual_connection_init,
74 			0
75 		};
76 
77 		g_mutex_lock (&registering);
78 		if (type == 0)
79 			type = g_type_register_static (BROWSER_TYPE_CONNECTION, "BrowserVirtualConnection", &info, 0);
80 		g_mutex_unlock (&registering);
81 	}
82 	return type;
83 }
84 
85 static void
source_cnc_busy_cb(G_GNUC_UNUSED BrowserConnection * bcnc,gboolean is_busy,G_GNUC_UNUSED const gchar * reason,BrowserConnection * virtual)86 source_cnc_busy_cb (G_GNUC_UNUSED BrowserConnection *bcnc, gboolean is_busy,
87 		    G_GNUC_UNUSED const gchar *reason, BrowserConnection *virtual)
88 {
89 	if (browser_connection_is_busy (virtual, NULL) != is_busy)
90 		g_signal_emit_by_name (virtual, "busy", is_busy,
91 				       is_busy ? _("Bound connection is used") : NULL);
92 }
93 
94 static void
m_busy(BrowserConnection * bcnc,gboolean is_busy,const gchar * reason)95 m_busy (BrowserConnection *bcnc, gboolean is_busy, const gchar *reason)
96 {
97 	/*
98 	 * declare all the source connections as busy
99 	 */
100 	GSList *list;
101 	if (! BROWSER_VIRTUAL_CONNECTION (bcnc)->priv->specs)
102 		return;
103 
104 	for (list = BROWSER_VIRTUAL_CONNECTION (bcnc)->priv->specs->parts; list; list = list->next) {
105 		BrowserVirtualConnectionPart *part;
106 		part = (BrowserVirtualConnectionPart*) list->data;
107 		if (part->part_type == BROWSER_VIRTUAL_CONNECTION_PART_CNC) {
108 			BrowserVirtualConnectionCnc *cnc;
109 			cnc = &(part->u.cnc);
110 			g_signal_handlers_block_by_func (cnc->source_cnc,
111 							 G_CALLBACK (source_cnc_busy_cb),
112 							 bcnc);
113 			if (browser_connection_is_busy (cnc->source_cnc, NULL) != is_busy)
114 				g_signal_emit_by_name (cnc->source_cnc, "busy", is_busy,
115 				is_busy ? _("Virtual connection using this connection is busy") : NULL);
116 			g_signal_handlers_unblock_by_func (cnc->source_cnc,
117 							   G_CALLBACK (source_cnc_busy_cb),
118 							   bcnc);
119 		}
120 	}
121 
122 	BROWSER_CONNECTION_CLASS (parent_class)->busy (bcnc, is_busy, reason);
123 }
124 
125 static void
browser_virtual_connection_class_init(BrowserVirtualConnectionClass * klass)126 browser_virtual_connection_class_init (BrowserVirtualConnectionClass * klass)
127 {
128 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
129 	parent_class = g_type_class_peek_parent (klass);
130 
131 	BROWSER_CONNECTION_CLASS (klass)->busy = m_busy;
132 
133 	/* Properties */
134         object_class->set_property = browser_virtual_connection_set_property;
135         object_class->get_property = browser_virtual_connection_get_property;
136 	g_object_class_install_property (object_class, PROP_SPECS,
137                                          g_param_spec_pointer ("specs", NULL,
138 							       "Specifications as a BrowserVirtualConnectionSpecs pointer",
139 							       G_PARAM_READABLE | G_PARAM_WRITABLE |
140 							       G_PARAM_CONSTRUCT_ONLY));
141 	object_class->dispose = browser_virtual_connection_dispose;
142 }
143 
144 static void
browser_virtual_connection_init(BrowserVirtualConnection * bcnc)145 browser_virtual_connection_init (BrowserVirtualConnection *bcnc)
146 {
147 	bcnc->priv = g_new0 (BrowserVirtualConnectionPrivate, 1);
148 }
149 
150 static void
browser_virtual_connection_dispose(GObject * object)151 browser_virtual_connection_dispose (GObject *object)
152 {
153 	BrowserVirtualConnection *bcnc;
154 
155 	g_return_if_fail (object != NULL);
156 	g_return_if_fail (BROWSER_IS_VIRTUAL_CONNECTION (object));
157 
158 	bcnc = BROWSER_VIRTUAL_CONNECTION (object);
159 
160 	if (bcnc->priv) {
161 		if (bcnc->priv->specs) {
162 			GSList *list;
163 			for (list = bcnc->priv->specs->parts; list; list = list->next) {
164 				BrowserVirtualConnectionPart *part;
165 				part = (BrowserVirtualConnectionPart*) list->data;
166 				if (part->part_type == BROWSER_VIRTUAL_CONNECTION_PART_CNC) {
167 					BrowserVirtualConnectionCnc *cnc;
168 					cnc = &(part->u.cnc);
169 					g_signal_handlers_disconnect_by_func (cnc->source_cnc,
170 									      G_CALLBACK (source_cnc_busy_cb),
171 									      bcnc);
172 				}
173 			}
174 			browser_virtual_connection_specs_free (bcnc->priv->specs);
175 		}
176 
177 		g_free (bcnc->priv);
178 		bcnc->priv = NULL;
179 	}
180 
181 	/* parent class */
182 	parent_class->dispose (object);
183 }
184 
185 static void
browser_virtual_connection_set_property(GObject * object,guint param_id,const GValue * value,GParamSpec * pspec)186 browser_virtual_connection_set_property (GObject *object,
187 					 guint param_id,
188 					 const GValue *value,
189 					 GParamSpec *pspec)
190 {
191         BrowserVirtualConnection *bcnc;
192 
193         bcnc = BROWSER_VIRTUAL_CONNECTION (object);
194         if (bcnc->priv) {
195                 switch (param_id) {
196                 case PROP_SPECS:
197 			bcnc->priv->specs = browser_virtual_connection_specs_copy (g_value_get_pointer (value));
198 			GSList *list;
199 			for (list = bcnc->priv->specs->parts; list; list = list->next) {
200 				BrowserVirtualConnectionPart *part;
201 				part = (BrowserVirtualConnectionPart*) list->data;
202 				if (part->part_type == BROWSER_VIRTUAL_CONNECTION_PART_CNC) {
203 					BrowserVirtualConnectionCnc *cnc;
204 					cnc = &(part->u.cnc);
205 					g_signal_connect (cnc->source_cnc, "busy",
206 							  G_CALLBACK (source_cnc_busy_cb), bcnc);
207 				}
208 			}
209 
210 			break;
211 		default:
212 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
213 			break;
214 		}
215 	}
216 }
217 
218 static void
browser_virtual_connection_get_property(GObject * object,guint param_id,GValue * value,GParamSpec * pspec)219 browser_virtual_connection_get_property (GObject *object,
220 					 guint param_id,
221 					 GValue *value,
222 					 GParamSpec *pspec)
223 {
224         BrowserVirtualConnection *bcnc;
225 
226         bcnc = BROWSER_VIRTUAL_CONNECTION (object);
227         if (bcnc->priv) {
228                 switch (param_id) {
229                 case PROP_SPECS:
230 			g_value_set_pointer (value, bcnc->priv->specs);
231 			break;
232 		default:
233 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
234 			break;
235 		}
236 	}
237 }
238 
239 
240 
241 typedef struct {
242         guint cncid;
243         GMainLoop *loop;
244         GError **error;
245 	GdaThreadWrapper *wrapper;
246 
247         /* out */
248         GdaConnection *cnc;
249 } MainloopData;
250 
251 static gboolean
check_for_cnc(MainloopData * data)252 check_for_cnc (MainloopData *data)
253 {
254         GdaConnection *cnc;
255         GError *lerror = NULL;
256         cnc = gda_thread_wrapper_fetch_result (data->wrapper, FALSE, data->cncid, &lerror);
257         if (cnc || (!cnc && lerror)) {
258                 /* waiting is finished! */
259                 data->cnc = cnc;
260                 if (lerror)
261                         g_propagate_error (data->error, lerror);
262                 g_main_loop_quit (data->loop);
263                 return FALSE;
264         }
265         return TRUE;
266 }
267 
268 /*
269  * executed in a sub thread
270  */
271 static GdaConnection *
sub_thread_open_cnc(BrowserVirtualConnectionSpecs * specs,GError ** error)272 sub_thread_open_cnc (BrowserVirtualConnectionSpecs *specs, GError **error)
273 {
274 #ifndef DUMMY
275 	/* create GDA virtual connection */
276 	static GdaVirtualProvider *provider = NULL;
277 	GdaConnection *virtual;
278 	if (!provider)
279 		provider = gda_vprovider_hub_new ();
280 
281 	virtual = gda_virtual_connection_open_extended (provider, GDA_CONNECTION_OPTIONS_THREAD_SAFE |
282 							GDA_CONNECTION_OPTIONS_AUTO_META_DATA, NULL);
283 
284 	/* add parts to connection as specified by @specs */
285 	GSList *list;
286 	for (list = specs->parts; list; list = list->next) {
287 		BrowserVirtualConnectionPart *part;
288 		part = (BrowserVirtualConnectionPart*) list->data;
289 		switch (part->part_type) {
290 		case BROWSER_VIRTUAL_CONNECTION_PART_MODEL: {
291 			BrowserVirtualConnectionModel *pm;
292 			pm = &(part->u.model);
293 			if (! gda_vconnection_data_model_add_model (GDA_VCONNECTION_DATA_MODEL (virtual),
294 								    pm->model, pm->table_name, error)) {
295 				g_object_unref (virtual);
296 				return NULL;
297 			}
298 			break;
299 		}
300 		case BROWSER_VIRTUAL_CONNECTION_PART_CNC: {
301 			BrowserVirtualConnectionCnc *scnc;
302 			scnc = &(part->u.cnc);
303 			if (!gda_vconnection_hub_add (GDA_VCONNECTION_HUB (virtual),
304 						      scnc->source_cnc->priv->cnc,
305 						      scnc->table_schema, error)) {
306 				g_object_unref (virtual);
307 				return NULL;
308 			}
309 			break;
310 		}
311 		default:
312 			g_assert_not_reached ();
313 		}
314 	}
315 
316 	return virtual;
317 #else /* DUMMY defined */
318         sleep (5);
319         g_set_error (error, GDA_TOOLS_ERROR, TOOLS_INTERNAL_COMMAND_ERROR,
320 		     "%s", "Timeout!!!");
321         return NULL;
322 #endif
323 }
324 
325 /**
326  * browser_virtual_connection_new
327  * @specs: the specifications of the virtual connection's contents
328  * @error: a place to store errors, or %NULL
329  *
330  * Creates a new #BrowserVirtualConnection connection.
331  *
332  * Returns: the new connection
333  */
334 BrowserConnection *
browser_virtual_connection_new(const BrowserVirtualConnectionSpecs * specs,GError ** error)335 browser_virtual_connection_new (const BrowserVirtualConnectionSpecs *specs, GError **error)
336 {
337 	/* open virtual GdaConnection in sub thread */
338 	GdaThreadWrapper *wrapper;
339 	guint cncid;
340 
341 	g_return_val_if_fail (specs, NULL);
342 
343 	wrapper = gda_thread_wrapper_new ();
344 	cncid = gda_thread_wrapper_execute (wrapper,
345                                             (GdaThreadWrapperFunc) sub_thread_open_cnc,
346                                             (gpointer) specs,
347                                             (GDestroyNotify) NULL,
348                                             error);
349         if (cncid == 0)
350                 return NULL;
351 
352 	GMainLoop *loop;
353         MainloopData data;
354 
355         loop = g_main_loop_new (NULL, FALSE);
356 	data.wrapper = wrapper;
357 	data.cncid = cncid;
358         data.error = error;
359         data.loop = loop;
360         data.cnc = NULL;
361 
362         g_timeout_add (200, (GSourceFunc) check_for_cnc, &data);
363         g_main_loop_run (loop);
364         g_main_loop_unref (loop);
365 	g_object_unref (wrapper);
366 
367 	/* create the BrowserConnection object */
368         if (data.cnc) {
369 		BrowserConnection *nbcnc;
370                 g_object_set (data.cnc, "monitor-wrapped-in-mainloop", TRUE, NULL);
371 		nbcnc = g_object_new (BROWSER_TYPE_VIRTUAL_CONNECTION, "specs", specs,
372 				      "gda-connection", data.cnc, NULL);
373 		g_object_unref (data.cnc);
374 		/*g_print ("BrowserVirtualConnection %p had TMP wrapper %p\n", nbcnc, wrapper);*/
375 
376 		return nbcnc;
377 	}
378 	else
379 		return NULL;
380 }
381 
382 /**
383  * browser_virtual_connection_modify_specs
384  * @bcnc: a #BrowserVirtualConnection connection
385  * @new_specs: a pointer to a #BrowserVirtualConnectionSpecs for the new specs
386  * @error: a place to store errors, or %NULL
387  *
388  * Returns: %TRUE if no error occurred
389  */
390 gboolean
browser_virtual_connection_modify_specs(BrowserVirtualConnection * bcnc,const BrowserVirtualConnectionSpecs * new_specs,GError ** error)391 browser_virtual_connection_modify_specs (BrowserVirtualConnection *bcnc,
392 					 const BrowserVirtualConnectionSpecs *new_specs, GError **error)
393 {
394 	g_return_val_if_fail (BROWSER_IS_VIRTUAL_CONNECTION (bcnc), FALSE);
395 	g_return_val_if_fail (new_specs, FALSE);
396 
397 	/* undo the current specs */
398 	GSList *list;
399 	GdaConnection *cnc;
400 	cnc = g_object_get_data (G_OBJECT (BROWSER_CONNECTION (bcnc)->priv->cnc), "gda-virtual-connection");
401 	for (list = bcnc->priv->specs->parts; list; list = bcnc->priv->specs->parts) {
402 		BrowserVirtualConnectionPart *part;
403 		part = (BrowserVirtualConnectionPart*) list->data;
404 		switch (part->part_type) {
405 		case BROWSER_VIRTUAL_CONNECTION_PART_MODEL: {
406 			BrowserVirtualConnectionModel *pm;
407 			pm = &(part->u.model);
408 			if (! gda_vconnection_data_model_remove (GDA_VCONNECTION_DATA_MODEL (cnc),
409 								 pm->table_name, error))
410 				return FALSE;
411 			break;
412 		}
413 		case BROWSER_VIRTUAL_CONNECTION_PART_CNC: {
414 			BrowserVirtualConnectionCnc *scnc;
415 			scnc = &(part->u.cnc);
416 			if (!gda_vconnection_hub_remove (GDA_VCONNECTION_HUB (cnc), scnc->source_cnc->priv->cnc,
417 							 error))
418 				return FALSE;
419 			break;
420 		}
421 		default:
422 			g_assert_not_reached ();
423 		}
424 
425 		/* get rid of @part */
426 		browser_virtual_connection_part_free (part);
427 		bcnc->priv->specs->parts = g_slist_remove (bcnc->priv->specs->parts, part);
428 	}
429 
430 	/* apply the new specs */
431 	browser_virtual_connection_specs_free (bcnc->priv->specs);
432 	bcnc->priv->specs = g_new0 (BrowserVirtualConnectionSpecs, 1);
433 
434 	for (list = new_specs->parts; list; list = list->next) {
435 		BrowserVirtualConnectionPart *part;
436 		part = (BrowserVirtualConnectionPart*) list->data;
437 		switch (part->part_type) {
438 		case BROWSER_VIRTUAL_CONNECTION_PART_MODEL: {
439 			BrowserVirtualConnectionModel *pm;
440 			pm = &(part->u.model);
441 			if (! gda_vconnection_data_model_add_model (GDA_VCONNECTION_DATA_MODEL (cnc),
442 								    pm->model, pm->table_name, error))
443 				return FALSE;
444 			break;
445 		}
446 		case BROWSER_VIRTUAL_CONNECTION_PART_CNC: {
447 			BrowserVirtualConnectionCnc *scnc;
448 			scnc = &(part->u.cnc);
449 			if (!gda_vconnection_hub_add (GDA_VCONNECTION_HUB (cnc), scnc->source_cnc->priv->cnc,
450 						      scnc->table_schema, error))
451 				return FALSE;
452 			break;
453 		}
454 		default:
455 			g_assert_not_reached ();
456 		}
457 
458 		/* add @part to bcnc->priv->specs */
459 		bcnc->priv->specs->parts = g_slist_append (bcnc->priv->specs->parts,
460 							   browser_virtual_connection_part_copy (part));
461 	}
462 
463 	return TRUE;
464 }
465 
466 
467 /*
468  * Spec manipulations
469  */
470 
471 /**
472  * browser_virtual_connection_part_copy
473  */
474 BrowserVirtualConnectionPart *
browser_virtual_connection_part_copy(const BrowserVirtualConnectionPart * part)475 browser_virtual_connection_part_copy (const BrowserVirtualConnectionPart *part)
476 {
477 	BrowserVirtualConnectionPart *npart;
478 	g_return_val_if_fail (part, NULL);
479 
480 	npart = g_new0 (BrowserVirtualConnectionPart, 1);
481 	npart->part_type = part->part_type;
482 
483 	switch (part->part_type) {
484 	case BROWSER_VIRTUAL_CONNECTION_PART_MODEL: {
485 		const BrowserVirtualConnectionModel *spm;
486 		BrowserVirtualConnectionModel *npm;
487 		spm = &(part->u.model);
488 		npm = &(npart->u.model);
489 		if (spm->table_name)
490 			npm->table_name = g_strdup (spm->table_name);
491 		if (spm->model)
492 			npm->model = g_object_ref (G_OBJECT (spm->model));
493 		break;
494 	}
495 	case BROWSER_VIRTUAL_CONNECTION_PART_CNC: {
496 		const BrowserVirtualConnectionCnc *scnc;
497 		BrowserVirtualConnectionCnc *ncnc;
498 		scnc = &(part->u.cnc);
499 		ncnc = &(npart->u.cnc);
500 		if (scnc->table_schema)
501 			ncnc->table_schema = g_strdup (scnc->table_schema);
502 		if (scnc->source_cnc)
503 			ncnc->source_cnc = g_object_ref (G_OBJECT (scnc->source_cnc));
504 		break;
505 	}
506 	default:
507 		g_assert_not_reached ();
508 	}
509 
510 	return npart;
511 }
512 
513 /**
514  * browser_virtual_connection_part_free
515  */
516 void
browser_virtual_connection_part_free(BrowserVirtualConnectionPart * part)517 browser_virtual_connection_part_free (BrowserVirtualConnectionPart *part)
518 {
519 	if (!part)
520 		return;
521 
522 	switch (part->part_type) {
523 	case BROWSER_VIRTUAL_CONNECTION_PART_MODEL: {
524 		BrowserVirtualConnectionModel *pm;
525 		pm = &(part->u.model);
526 		g_free (pm->table_name);
527 		if (pm->model)
528 			g_object_unref (pm->model);
529 		break;
530 	}
531 	case BROWSER_VIRTUAL_CONNECTION_PART_CNC: {
532 		BrowserVirtualConnectionCnc *cnc;
533 		cnc = &(part->u.cnc);
534 		g_free (cnc->table_schema);
535 		if (cnc->source_cnc)
536 			g_object_unref (cnc->source_cnc);
537 		break;
538 	}
539 	default:
540 		g_assert_not_reached ();
541 	}
542 }
543 
544 /**
545  * browser_virtual_connection_specs_copy
546  */
547 BrowserVirtualConnectionSpecs *
browser_virtual_connection_specs_copy(const BrowserVirtualConnectionSpecs * specs)548 browser_virtual_connection_specs_copy (const BrowserVirtualConnectionSpecs *specs)
549 {
550 	BrowserVirtualConnectionSpecs *ns;
551 	GSList *list;
552 
553 	g_return_val_if_fail (specs, NULL);
554 
555 	ns = g_new0 (BrowserVirtualConnectionSpecs, 1);
556 	for (list = specs->parts; list; list = list->next) {
557 		BrowserVirtualConnectionPart *npart;
558 		npart = browser_virtual_connection_part_copy ((BrowserVirtualConnectionPart*) list->data);
559 		ns->parts = g_slist_prepend (ns->parts, npart);
560 	}
561 	ns->parts = g_slist_reverse (ns->parts);
562 
563 	return ns;
564 }
565 
566 /**
567  * browser_virtual_connection_specs_free
568  */
569 void
browser_virtual_connection_specs_free(BrowserVirtualConnectionSpecs * specs)570 browser_virtual_connection_specs_free (BrowserVirtualConnectionSpecs *specs)
571 {
572 	if (!specs)
573 		return;
574 	g_slist_foreach (specs->parts, (GFunc) browser_virtual_connection_part_free, NULL);
575 	g_slist_free (specs->parts);
576 	g_free (specs);
577 }
578