1 /* gom-resource.c
2 *
3 * Copyright (C) 2011 Christian Hergert <chris@dronelabs.com>
4 *
5 * This file is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This file 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 GNU
13 * Lesser 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, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <sqlite3.h>
20 #include <string.h>
21
22 #include "gom-command.h"
23 #include "gom-command-builder.h"
24 #include "gom-error.h"
25 #include "gom-filter.h"
26 #include "gom-repository.h"
27 #include "gom-resource.h"
28 #include "gom-resource-priv.h"
29 #include "reserved-keywords.h"
30
31 G_DEFINE_ABSTRACT_TYPE(GomResource, gom_resource, G_TYPE_OBJECT)
32
33 struct _GomResourcePrivate
34 {
35 GomRepository *repository;
36 gboolean is_from_table;
37 };
38
39 enum
40 {
41 PROP_0,
42 PROP_REPOSITORY,
43 LAST_PROP
44 };
45
46 static GParamSpec *gParamSpecs[LAST_PROP];
47
48 void
gom_resource_class_set_primary_key(GomResourceClass * resource_class,const gchar * primary_key)49 gom_resource_class_set_primary_key (GomResourceClass *resource_class,
50 const gchar *primary_key)
51 {
52 GParamSpec *pspec;
53 const GValue *value;
54
55 g_return_if_fail(GOM_IS_RESOURCE_CLASS(resource_class));
56 g_return_if_fail(primary_key != NULL);
57 g_return_if_fail(strlen(primary_key) <= sizeof(resource_class->primary_key));
58
59 pspec = g_object_class_find_property(G_OBJECT_CLASS(resource_class), primary_key);
60 if (!pspec) {
61 g_warning("Property for primary key '%s' (class %s) isn't declared yet. Are you running gom_resource_class_set_primary_key() too early?",
62 primary_key, G_OBJECT_CLASS_NAME(resource_class));
63 return;
64 }
65
66 if (pspec->flags & G_PARAM_CONSTRUCT_ONLY) {
67 g_warning("Property for primary key '%s' (class %s) is declared as construct-only. This will not work as expected.",
68 primary_key, G_OBJECT_CLASS_NAME(resource_class));
69 return;
70 }
71
72 /* Same check as in has_primary_key() */
73 value = g_param_spec_get_default_value (pspec);
74 if (value->data[0].v_pointer &&
75 *((char *) value->data[0].v_pointer) != '\0') {
76 g_warning("Property for primary key '%s' (class %s) has a non-NULL/non-zero default value. This will not work as expected.",
77 primary_key, G_OBJECT_CLASS_NAME(resource_class));
78 return;
79 }
80
81 g_snprintf(resource_class->primary_key,
82 sizeof(resource_class->primary_key),
83 "%s", primary_key);
84 }
85
86 void
gom_resource_class_set_property_new_in_version(GomResourceClass * resource_class,const gchar * property_name,guint version)87 gom_resource_class_set_property_new_in_version (GomResourceClass *resource_class,
88 const gchar *property_name,
89 guint version)
90 {
91 GParamSpec *pspec;
92
93 g_return_if_fail(GOM_IS_RESOURCE_CLASS(resource_class));
94 g_return_if_fail(property_name != NULL);
95 g_return_if_fail(version >= 1);
96
97 pspec = g_object_class_find_property(G_OBJECT_CLASS(resource_class), property_name);
98 g_assert(pspec);
99
100 /* See is_new_in_version() in gom-repository.c for the reasoning
101 * behind the "- 1" */
102 g_param_spec_set_qdata(pspec, GOM_RESOURCE_NEW_IN_VERSION, GINT_TO_POINTER(version - 1));
103 }
104
105 void
gom_resource_class_set_property_set_mapped(GomResourceClass * resource_class,const gchar * property_name,gboolean is_mapped)106 gom_resource_class_set_property_set_mapped (GomResourceClass *resource_class,
107 const gchar *property_name,
108 gboolean is_mapped)
109 {
110 GParamSpec *pspec;
111
112 g_return_if_fail(GOM_IS_RESOURCE_CLASS(resource_class));
113 g_return_if_fail(property_name != NULL);
114
115 pspec = g_object_class_find_property(G_OBJECT_CLASS(resource_class), property_name);
116 g_assert(pspec);
117
118 g_param_spec_set_qdata(pspec, GOM_RESOURCE_NOT_MAPPED, GINT_TO_POINTER(!is_mapped));
119 }
120
121 /**
122 * gom_resource_class_set_property_transform: (skip)
123 */
124 void
gom_resource_class_set_property_transform(GomResourceClass * resource_class,const gchar * property_name,GomResourceToBytesFunc to_bytes_func,GomResourceFromBytesFunc from_bytes_func)125 gom_resource_class_set_property_transform (GomResourceClass *resource_class,
126 const gchar *property_name,
127 GomResourceToBytesFunc to_bytes_func,
128 GomResourceFromBytesFunc from_bytes_func)
129 {
130 GParamSpec *pspec;
131
132 g_return_if_fail(GOM_IS_RESOURCE_CLASS(resource_class));
133 g_return_if_fail(property_name != NULL);
134 g_return_if_fail(to_bytes_func != NULL);
135 g_return_if_fail(from_bytes_func != NULL);
136
137 pspec = g_object_class_find_property(G_OBJECT_CLASS(resource_class), property_name);
138 g_assert(pspec);
139
140 g_param_spec_set_qdata(pspec, GOM_RESOURCE_TO_BYTES_FUNC, to_bytes_func);
141 g_param_spec_set_qdata(pspec, GOM_RESOURCE_FROM_BYTES_FUNC, from_bytes_func);
142 }
143
144 void
gom_resource_class_set_property_to_bytes(GomResourceClass * resource_class,const gchar * property_name,GomResourceToBytesFunc to_bytes_func,GDestroyNotify notify)145 gom_resource_class_set_property_to_bytes (GomResourceClass *resource_class,
146 const gchar *property_name,
147 GomResourceToBytesFunc to_bytes_func,
148 GDestroyNotify notify)
149 {
150 GParamSpec *pspec;
151
152 g_return_if_fail(GOM_IS_RESOURCE_CLASS(resource_class));
153 g_return_if_fail(property_name != NULL);
154 g_return_if_fail(to_bytes_func != NULL);
155
156 pspec = g_object_class_find_property(G_OBJECT_CLASS(resource_class), property_name);
157 g_assert(pspec);
158
159 g_param_spec_set_qdata(pspec, GOM_RESOURCE_TO_BYTES_FUNC, to_bytes_func);
160 }
161
162 void
gom_resource_class_set_property_from_bytes(GomResourceClass * resource_class,const gchar * property_name,GomResourceFromBytesFunc from_bytes_func,GDestroyNotify notify)163 gom_resource_class_set_property_from_bytes (GomResourceClass *resource_class,
164 const gchar *property_name,
165 GomResourceFromBytesFunc from_bytes_func,
166 GDestroyNotify notify)
167 {
168 GParamSpec *pspec;
169
170 g_return_if_fail(GOM_IS_RESOURCE_CLASS(resource_class));
171 g_return_if_fail(property_name != NULL);
172 g_return_if_fail(from_bytes_func != NULL);
173
174 pspec = g_object_class_find_property(G_OBJECT_CLASS(resource_class), property_name);
175 g_assert(pspec);
176
177 g_param_spec_set_qdata(pspec, GOM_RESOURCE_FROM_BYTES_FUNC, from_bytes_func);
178 }
179
180 void
gom_resource_class_set_reference(GomResourceClass * resource_class,const gchar * property_name,const gchar * ref_table_name,const gchar * ref_property_name)181 gom_resource_class_set_reference (GomResourceClass *resource_class,
182 const gchar *property_name,
183 const gchar *ref_table_name,
184 const gchar *ref_property_name)
185 {
186 GParamSpec *pspec;
187
188 g_return_if_fail(GOM_IS_RESOURCE_CLASS(resource_class));
189 g_return_if_fail(property_name != NULL);
190 g_return_if_fail(ref_property_name != NULL);
191
192 pspec = g_object_class_find_property(G_OBJECT_CLASS(resource_class), property_name);
193 g_assert(pspec);
194
195 if (ref_table_name == NULL)
196 ref_table_name = G_OBJECT_CLASS_NAME(resource_class);
197
198 g_param_spec_set_qdata_full(pspec, GOM_RESOURCE_REF_TABLE_CLASS,
199 g_strdup(ref_table_name), g_free);
200 g_param_spec_set_qdata_full(pspec, GOM_RESOURCE_REF_PROPERTY_NAME,
201 g_strdup(ref_property_name), g_free);
202 }
203
204 static gboolean
is_valid_table_name(const gchar * table)205 is_valid_table_name (const gchar *table)
206 {
207 guint i;
208
209 for (i = 0; i < G_N_ELEMENTS (reserved_keywords); i++) {
210 if (g_ascii_strcasecmp (reserved_keywords[i], table) == 0)
211 return FALSE;
212 }
213
214 return TRUE;
215 }
216
217 void
gom_resource_class_set_table(GomResourceClass * resource_class,const gchar * table)218 gom_resource_class_set_table (GomResourceClass *resource_class,
219 const gchar *table)
220 {
221 g_return_if_fail(GOM_IS_RESOURCE_CLASS(resource_class));
222 g_return_if_fail(table != NULL);
223 g_return_if_fail(strlen(table) <= sizeof(resource_class->table));
224 g_return_if_fail(is_valid_table_name(table));
225
226 g_snprintf(resource_class->table,
227 sizeof(resource_class->table),
228 "%s", table);
229 }
230
231 void
gom_resource_class_set_unique(GomResourceClass * resource_class,const gchar * property_name)232 gom_resource_class_set_unique (GomResourceClass *resource_class,
233 const gchar *property_name)
234 {
235 GParamSpec *pspec;
236
237 g_return_if_fail(GOM_IS_RESOURCE_CLASS(resource_class));
238 g_return_if_fail(property_name != NULL);
239
240 pspec = g_object_class_find_property(G_OBJECT_CLASS(resource_class), property_name);
241 if (!pspec) {
242 g_warning("Unique property '%s' isn't declared yet. Are you running gom_resource_class_set_unique() too early?",
243 property_name);
244 return;
245 }
246
247 g_param_spec_set_qdata_full(pspec, GOM_RESOURCE_UNIQUE,
248 GUINT_TO_POINTER (TRUE), NULL);
249 }
250
251 void
gom_resource_class_set_notnull(GomResourceClass * resource_class,const gchar * property_name)252 gom_resource_class_set_notnull (GomResourceClass *resource_class,
253 const gchar *property_name)
254 {
255 GParamSpec *pspec;
256
257 g_return_if_fail(GOM_IS_RESOURCE_CLASS(resource_class));
258 g_return_if_fail(property_name != NULL);
259
260 pspec = g_object_class_find_property(G_OBJECT_CLASS(resource_class), property_name);
261 if (!pspec) {
262 g_warning("NOT NULL property '%s' isn't declared yet. Are you running gom_resource_class_set_notnull() too early?",
263 property_name);
264 return;
265 }
266
267 g_param_spec_set_qdata_full(pspec, GOM_RESOURCE_NOTNULL,
268 GUINT_TO_POINTER (TRUE), NULL);
269 }
270
271 static GomRepository *
gom_resource_get_repository(GomResource * resource)272 gom_resource_get_repository (GomResource *resource)
273 {
274 g_return_val_if_fail(GOM_IS_RESOURCE(resource), NULL);
275 return resource->priv->repository;
276 }
277
278 static void
gom_resource_set_repository(GomResource * resource,GomRepository * repository)279 gom_resource_set_repository (GomResource *resource,
280 GomRepository *repository)
281 {
282 GomResourcePrivate *priv;
283 GomRepository *old;
284
285 g_return_if_fail(GOM_IS_RESOURCE(resource));
286 g_return_if_fail(!repository || GOM_IS_REPOSITORY(repository));
287
288 priv = resource->priv;
289
290 old = priv->repository;
291 if (old) {
292 g_object_remove_weak_pointer(G_OBJECT(priv->repository),
293 (gpointer *)&priv->repository);
294 priv->repository = NULL;
295 }
296
297 if (repository) {
298 priv->repository = repository;
299 g_object_add_weak_pointer(G_OBJECT(priv->repository),
300 (gpointer *)&priv->repository);
301 g_object_notify_by_pspec(G_OBJECT(resource),
302 gParamSpecs[PROP_REPOSITORY]);
303 }
304 }
305
306 gboolean
gom_resource_do_delete(GomResource * resource,GomAdapter * adapter,GError ** error)307 gom_resource_do_delete (GomResource *resource,
308 GomAdapter *adapter,
309 GError **error)
310 {
311 GomCommandBuilder *builder;
312 GType resource_type;
313
314 g_return_val_if_fail(GOM_IS_RESOURCE(resource), FALSE);
315 g_return_val_if_fail(GOM_IS_ADAPTER(adapter), FALSE);
316
317 resource_type = G_TYPE_FROM_INSTANCE(resource);
318 builder = g_object_new(GOM_TYPE_COMMAND_BUILDER,
319 "adapter", adapter,
320 NULL);
321
322 do {
323 GomResourceClass *klass;
324 GParamSpec *pspec;
325 GomCommand *command;
326 GomFilter *filter;
327 GArray *values;
328 GValue value = { 0 };
329 gchar *sql;
330
331 klass = g_type_class_peek(resource_type);
332 g_assert(GOM_IS_RESOURCE_CLASS(klass));
333
334 pspec = g_object_class_find_property(G_OBJECT_CLASS(klass),
335 klass->primary_key);
336 g_assert(pspec);
337
338 g_value_init(&value, pspec->value_type);
339 g_object_get_property(G_OBJECT(resource), klass->primary_key, &value);
340 sql = g_strdup_printf("'%s'.'%s' = ?", klass->table, klass->primary_key);
341 values = g_array_sized_new(FALSE, FALSE, sizeof(GValue), 1);
342 g_array_append_val(values, value);
343 filter = gom_filter_new_sql(sql, values);
344 g_free(sql);
345 memset(&value, 0, sizeof value);
346 g_array_unref(values);
347 g_object_set(builder,
348 "filter", filter,
349 "resource-type", resource_type,
350 NULL);
351 g_object_unref(filter);
352
353 command = gom_command_builder_build_delete(builder);
354 if (!gom_command_execute(command, NULL, error)) {
355 g_object_unref(command);
356 g_object_unref(builder);
357 return FALSE;
358 }
359 g_object_unref(command);
360 } while ((resource_type = g_type_parent(resource_type)) != GOM_TYPE_RESOURCE);
361
362 g_object_unref(builder);
363
364 return TRUE;
365 }
366
367 static void
gom_resource_delete_cb(GomAdapter * adapter,gpointer user_data)368 gom_resource_delete_cb (GomAdapter *adapter,
369 gpointer user_data)
370 {
371 GSimpleAsyncResult *simple = user_data;
372 GomResource *resource;
373 gboolean ret;
374 GError *error = NULL;
375 GAsyncQueue *queue;
376
377 g_return_if_fail(G_IS_SIMPLE_ASYNC_RESULT(simple));
378 resource = GOM_RESOURCE(g_async_result_get_source_object(G_ASYNC_RESULT(simple)));
379 g_return_if_fail(GOM_IS_RESOURCE(resource));
380
381 queue = g_object_get_data(G_OBJECT(simple), "queue");
382
383 if (!(ret = gom_resource_do_delete(resource, adapter, &error))) {
384 g_simple_async_result_take_error(simple, error);
385 }
386
387 g_simple_async_result_set_op_res_gboolean(simple, ret);
388 if (!queue)
389 g_simple_async_result_complete_in_idle(simple);
390 else
391 g_async_queue_push(queue, GINT_TO_POINTER(TRUE));
392 g_object_unref(resource);
393 }
394
395 /**
396 * gom_resource_delete_sync:
397 * @resource: (in): A #GomResource.
398 * @error: (out): A location for a #GError, or %NULL.
399 *
400 * Synchronously deletes a resource. This may only be called from inside a
401 * callback to gom_adapter_queue_write().
402 *
403 * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
404 */
405 gboolean
gom_resource_delete_sync(GomResource * resource,GError ** error)406 gom_resource_delete_sync (GomResource *resource,
407 GError **error)
408 {
409 GomResourcePrivate *priv;
410 GomAdapter *adapter;
411 GAsyncQueue *queue;
412 GSimpleAsyncResult *simple;
413 gboolean ret;
414
415 g_return_val_if_fail(GOM_IS_RESOURCE(resource), FALSE);
416
417 priv = resource->priv;
418
419 if (!priv->repository) {
420 g_warning("Cannot save resource, no repository set!");
421 return FALSE;
422 }
423
424 queue = g_async_queue_new();
425
426 simple = g_simple_async_result_new(G_OBJECT(resource), NULL, NULL,
427 gom_resource_delete_sync);
428 adapter = gom_repository_get_adapter(priv->repository);
429 g_object_set_data(G_OBJECT(simple), "queue", queue);
430 g_assert(GOM_IS_ADAPTER(adapter));
431
432 gom_adapter_queue_write(adapter, gom_resource_delete_cb, simple);
433 g_async_queue_pop(queue);
434 g_async_queue_unref(queue);
435
436 if (!(ret = g_simple_async_result_get_op_res_gboolean(simple))) {
437 g_simple_async_result_propagate_error(simple, error);
438 }
439 g_object_unref(simple);
440
441 return ret;
442 }
443
444 void
gom_resource_delete_async(GomResource * resource,GAsyncReadyCallback callback,gpointer user_data)445 gom_resource_delete_async (GomResource *resource,
446 GAsyncReadyCallback callback,
447 gpointer user_data)
448 {
449 GomResourcePrivate *priv;
450 GSimpleAsyncResult *simple;
451 GomAdapter *adapter;
452
453 g_return_if_fail(GOM_IS_RESOURCE(resource));
454
455 priv = resource->priv;
456
457 if (!priv->repository) {
458 g_warning("Cannot delete resource, no repository set!");
459 return;
460 }
461
462 simple = g_simple_async_result_new(G_OBJECT(resource), callback, user_data,
463 gom_resource_delete_async);
464 adapter = gom_repository_get_adapter(priv->repository);
465 g_assert(GOM_IS_ADAPTER(adapter));
466 gom_adapter_queue_write(adapter, gom_resource_delete_cb, simple);
467 }
468
469 gboolean
gom_resource_delete_finish(GomResource * resource,GAsyncResult * result,GError ** error)470 gom_resource_delete_finish (GomResource *resource,
471 GAsyncResult *result,
472 GError **error)
473 {
474 GSimpleAsyncResult *simple = (GSimpleAsyncResult *)result;
475 gboolean ret;
476
477 g_return_val_if_fail(GOM_IS_RESOURCE(resource), FALSE);
478 g_return_val_if_fail(G_IS_SIMPLE_ASYNC_RESULT(simple), FALSE);
479
480 if (!(ret = g_simple_async_result_get_op_res_gboolean(simple))) {
481 g_simple_async_result_propagate_error(simple, error);
482 }
483 g_object_unref(simple);
484
485 return ret;
486 }
487
488 gboolean
gom_resource_has_dynamic_pkey(GType type)489 gom_resource_has_dynamic_pkey (GType type)
490 {
491 GomResourceClass *klass;
492 GParamSpec *pspec;
493 gboolean ret = FALSE;
494
495 g_assert(type);
496 g_assert(g_type_is_a(type, GOM_TYPE_RESOURCE));
497
498 klass = g_type_class_ref(type);
499 g_assert(GOM_IS_RESOURCE_CLASS(klass));
500
501 pspec = g_object_class_find_property(G_OBJECT_CLASS(klass), klass->primary_key);
502 g_assert(pspec);
503
504 switch (pspec->value_type) {
505 case G_TYPE_INT:
506 case G_TYPE_INT64:
507 case G_TYPE_UINT:
508 case G_TYPE_UINT64:
509 ret = TRUE;
510 break;
511 default:
512 break;
513 }
514
515 g_type_class_unref(klass);
516
517 return ret;
518 }
519
520 static void
set_pkey(GomResource * resource,GValue * value)521 set_pkey (GomResource *resource,
522 GValue *value)
523 {
524 GParamSpec *pspec;
525 GValue dst_value = { 0 };
526
527 pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(resource),
528 GOM_RESOURCE_GET_CLASS(resource)->primary_key);
529 g_assert(pspec);
530 g_value_init(&dst_value, pspec->value_type);
531 g_value_transform(value, &dst_value);
532 g_object_set_property(G_OBJECT(resource), pspec->name, &dst_value);
533 g_value_unset(&dst_value);
534 }
535
536 static gboolean
has_primary_key(GomResource * resource)537 has_primary_key (GomResource *resource)
538 {
539 GomResourceClass *klass;
540 GParamSpec *pspec;
541 gboolean ret;
542 GValue value = { 0 };
543
544 g_return_val_if_fail(GOM_IS_RESOURCE(resource), FALSE);
545
546 klass = GOM_RESOURCE_GET_CLASS(resource);
547
548 pspec = g_object_class_find_property(G_OBJECT_CLASS(klass),
549 klass->primary_key);
550 if (!pspec)
551 return FALSE;
552
553 g_value_init(&value, pspec->value_type);
554 g_object_get_property(G_OBJECT(resource), klass->primary_key, &value);
555 ret = !!value.data[0].v_pointer;
556 g_value_unset(&value);
557
558 return ret;
559 }
560
561 void
gom_resource_set_post_save_properties(GomResource * resource)562 gom_resource_set_post_save_properties (GomResource *resource)
563 {
564 GValue *value;
565
566 gom_resource_set_is_from_table(resource,
567 GPOINTER_TO_INT(g_object_get_data(G_OBJECT(resource), "is-from-table")));
568 g_object_set_data (G_OBJECT(resource), "is-from-table", NULL);
569
570 value = g_object_get_data(G_OBJECT(resource), "row-id");
571 if (!value)
572 return;
573 set_pkey(resource, value);
574 g_object_set_data(G_OBJECT(resource), "row-id", NULL);
575 }
576
577 static void
free_save_cmds(gpointer data)578 free_save_cmds (gpointer data)
579 {
580 GList *cmds = data;
581
582 g_list_free_full (cmds, g_object_unref);
583 }
584
585 static void
value_free(gpointer data)586 value_free (gpointer data)
587 {
588 GValue *value = data;
589 g_value_unset (value);
590 g_free (value);
591 }
592
593 void
gom_resource_build_save_cmd(GomResource * resource,GomAdapter * adapter)594 gom_resource_build_save_cmd (GomResource *resource,
595 GomAdapter *adapter)
596 {
597 GomCommandBuilder *builder;
598 gboolean has_pkey, is_insert;
599 GSList *types = NULL;
600 GSList *iter;
601 GType resource_type;
602 GList *cmds = NULL;
603
604 resource_type = G_TYPE_FROM_INSTANCE(resource);
605 g_assert(g_type_is_a(resource_type, GOM_TYPE_RESOURCE));
606
607 builder = g_object_new(GOM_TYPE_COMMAND_BUILDER,
608 "adapter", adapter,
609 NULL);
610
611 has_pkey = has_primary_key(resource);
612 if (has_pkey) {
613 /* Could be an insert for a non-automatic primary key,
614 * or an update */
615 is_insert = !resource->priv->is_from_table;
616 } else {
617 is_insert = TRUE;
618 }
619
620 g_object_set_data (G_OBJECT (resource), "is-insert", GINT_TO_POINTER (is_insert));
621
622 do {
623 types = g_slist_prepend(types, GINT_TO_POINTER(resource_type));
624 } while ((resource_type = g_type_parent(resource_type)) != GOM_TYPE_RESOURCE);
625
626 for (iter = types; iter; iter = iter->next) {
627 GomCommand *command;
628
629 resource_type = (GType) iter->data;
630
631 g_object_set(builder,
632 "resource-type", resource_type,
633 NULL);
634
635 if (is_insert) {
636 command = gom_command_builder_build_insert(builder, resource);
637 } else {
638 command = gom_command_builder_build_update(builder, resource);
639 }
640
641 if (is_insert && gom_resource_has_dynamic_pkey(resource_type))
642 is_insert = FALSE;
643
644 cmds = g_list_prepend (cmds, command);
645 }
646
647 cmds = g_list_reverse (cmds);
648 g_object_set_data_full (G_OBJECT(resource), "save-commands", cmds, free_save_cmds);
649
650 g_slist_free(types);
651 g_object_unref (builder);
652 }
653
654 gboolean
gom_resource_do_save(GomResource * resource,GomAdapter * adapter,GError ** error)655 gom_resource_do_save (GomResource *resource,
656 GomAdapter *adapter,
657 GError **error)
658 {
659 gboolean ret = FALSE;
660 gboolean is_insert;
661 gint64 row_id = -1;
662 GType resource_type;
663 GList *cmds, *l;
664
665 g_return_val_if_fail(GOM_IS_RESOURCE(resource), FALSE);
666 g_return_val_if_fail(GOM_IS_ADAPTER(adapter), FALSE);
667
668 resource_type = G_TYPE_FROM_INSTANCE(resource);
669 g_assert(g_type_is_a(resource_type, GOM_TYPE_RESOURCE));
670
671 is_insert = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(resource), "is-insert"));
672 cmds = g_object_get_data(G_OBJECT(resource), "save-commands");
673
674 for (l = cmds; l != NULL; l = l->next) {
675 GomCommand *command = l->data;
676
677 if (!gom_command_execute(command, NULL, error))
678 goto out;
679
680 if (is_insert && row_id == -1 && gom_resource_has_dynamic_pkey(resource_type)) {
681 sqlite3 *handle = gom_adapter_get_handle(adapter);
682 GValue *value;
683
684 row_id = sqlite3_last_insert_rowid(handle);
685 value = g_new0 (GValue, 1);
686 g_value_init(value, G_TYPE_INT64);
687 g_value_set_int64(value, row_id);
688 g_object_set_data_full(G_OBJECT(resource), "row-id", value, value_free);
689
690 g_object_set_data (G_OBJECT(resource), "is-from-table", GINT_TO_POINTER(TRUE));
691
692 is_insert = FALSE;
693 }
694 }
695
696 ret = TRUE;
697
698 out:
699 g_object_set_data (G_OBJECT (resource), "save-commands", NULL);
700 g_object_set_data (G_OBJECT (resource), "is-insert", NULL);
701
702 return ret;
703 }
704
705 static void
gom_resource_save_cb(GomAdapter * adapter,gpointer user_data)706 gom_resource_save_cb (GomAdapter *adapter,
707 gpointer user_data)
708 {
709 GSimpleAsyncResult *simple = user_data;
710 GomResource *resource;
711 gboolean ret;
712 GError *error = NULL;
713 GAsyncQueue *queue;
714
715 g_return_if_fail(GOM_IS_ADAPTER(adapter));
716 g_return_if_fail(G_IS_SIMPLE_ASYNC_RESULT(simple));
717
718 resource = GOM_RESOURCE(g_async_result_get_source_object(G_ASYNC_RESULT(simple)));
719 g_assert(GOM_IS_RESOURCE(resource));
720
721 queue = g_object_get_data(G_OBJECT(simple), "queue");
722
723 if (!(ret = gom_resource_do_save(resource, adapter, &error))) {
724 g_simple_async_result_take_error(simple, error);
725 } else {
726 g_object_set_data(G_OBJECT(resource), "is-from-table", GINT_TO_POINTER(TRUE));
727 }
728
729 g_simple_async_result_set_op_res_gboolean(simple, ret);
730 if (!queue)
731 g_simple_async_result_complete_in_idle(simple);
732 else
733 g_async_queue_push(queue, GINT_TO_POINTER(TRUE));
734 g_object_unref(resource);
735 }
736
737 /**
738 * gom_resource_save_sync:
739 * @resource: (in): A #GomResource.
740 * @error: (out): A location for a #GError, or %NULL.
741 *
742 * Returns: %TRUE if successful; otherwise %FALSE.
743 */
744 gboolean
gom_resource_save_sync(GomResource * resource,GError ** error)745 gom_resource_save_sync (GomResource *resource,
746 GError **error)
747 {
748 GomResourcePrivate *priv;
749 GomAdapter *adapter;
750 GAsyncQueue *queue;
751 GSimpleAsyncResult *simple;
752 gboolean ret;
753
754 g_return_val_if_fail(GOM_IS_RESOURCE(resource), FALSE);
755
756 priv = resource->priv;
757
758 if (!priv->repository) {
759 g_set_error(error, GOM_ERROR, GOM_ERROR_COMMAND_NO_REPOSITORY,
760 "Cannot save resource, no repository set");
761 return FALSE;
762 }
763
764 queue = g_async_queue_new();
765
766 simple = g_simple_async_result_new(G_OBJECT(resource), NULL, NULL,
767 gom_resource_save_sync);
768 adapter = gom_repository_get_adapter(priv->repository);
769 g_object_set_data(G_OBJECT(simple), "queue", queue);
770 g_assert(GOM_IS_ADAPTER(adapter));
771
772 gom_resource_build_save_cmd(resource, adapter);
773
774 gom_adapter_queue_write(adapter, gom_resource_save_cb, simple);
775 g_async_queue_pop(queue);
776 g_async_queue_unref(queue);
777
778 if (!(ret = g_simple_async_result_get_op_res_gboolean(simple))) {
779 g_simple_async_result_propagate_error(simple, error);
780 }
781
782 if (ret)
783 gom_resource_set_post_save_properties(resource);
784
785 g_object_unref(simple);
786
787 return ret;
788 }
789
790 void
gom_resource_save_async(GomResource * resource,GAsyncReadyCallback callback,gpointer user_data)791 gom_resource_save_async (GomResource *resource,
792 GAsyncReadyCallback callback,
793 gpointer user_data)
794 {
795 GomResourcePrivate *priv;
796 GSimpleAsyncResult *simple;
797 GomAdapter *adapter;
798
799 g_return_if_fail(GOM_IS_RESOURCE(resource));
800 g_return_if_fail(callback != NULL);
801
802 priv = resource->priv;
803
804 if (!priv->repository) {
805 g_warning("Cannot save resource, no repository set!");
806 return;
807 }
808
809 simple = g_simple_async_result_new(G_OBJECT(resource), callback, user_data,
810 gom_resource_save_async);
811 adapter = gom_repository_get_adapter(priv->repository);
812 g_assert(GOM_IS_ADAPTER(adapter));
813 gom_resource_build_save_cmd(resource, adapter);
814 gom_adapter_queue_write(adapter, gom_resource_save_cb, simple);
815 }
816
817 gboolean
gom_resource_save_finish(GomResource * resource,GAsyncResult * result,GError ** error)818 gom_resource_save_finish (GomResource *resource,
819 GAsyncResult *result,
820 GError **error)
821 {
822 GSimpleAsyncResult *simple = (GSimpleAsyncResult *)result;
823 gboolean ret;
824
825 g_return_val_if_fail(GOM_IS_RESOURCE(resource), FALSE);
826 g_return_val_if_fail(G_IS_SIMPLE_ASYNC_RESULT(simple), FALSE);
827
828 if (!(ret = g_simple_async_result_get_op_res_gboolean(simple))) {
829 g_simple_async_result_propagate_error(simple, error);
830 }
831
832 if (ret)
833 gom_resource_set_post_save_properties(resource);
834
835 g_object_unref(simple);
836
837 return ret;
838 }
839
840 static void
gom_resource_fetch_m2m_cb(GomAdapter * adapter,gpointer user_data)841 gom_resource_fetch_m2m_cb (GomAdapter *adapter,
842 gpointer user_data)
843 {
844 GSimpleAsyncResult *simple = user_data;
845 GomCommandBuilder *builder = NULL;
846 GomResourceGroup *group;
847 GomRepository *repository;
848 const gchar *m2m_table;
849 GomResource *resource;
850 GomCommand *command = NULL;
851 GomCursor *cursor = NULL;
852 GomFilter *filter = NULL;
853 GError *error = NULL;
854 guint count = 0;
855 GType resource_type;
856
857 g_return_if_fail(GOM_IS_ADAPTER(adapter));
858 g_return_if_fail(G_IS_SIMPLE_ASYNC_RESULT(simple));
859
860 m2m_table = g_object_get_data(G_OBJECT(simple), "m2m-table");
861 resource_type = (GType) g_object_get_data(G_OBJECT(simple), "resource-type");
862 filter = g_object_get_data(G_OBJECT(simple), "filter");
863 resource = GOM_RESOURCE(g_async_result_get_source_object(G_ASYNC_RESULT(simple)));
864 repository = gom_resource_get_repository(resource);
865
866 g_assert(GOM_IS_RESOURCE(resource));
867 g_assert(m2m_table);
868 g_assert(g_type_is_a(resource_type, GOM_TYPE_RESOURCE));
869 g_assert(!filter || GOM_IS_FILTER(filter));
870 g_assert(GOM_IS_REPOSITORY(repository));
871
872 builder = g_object_new(GOM_TYPE_COMMAND_BUILDER,
873 "adapter", adapter,
874 "filter", filter,
875 "resource-type", resource_type,
876 "m2m-table", m2m_table,
877 "m2m-type", G_TYPE_FROM_INSTANCE(resource),
878 NULL);
879
880 command = gom_command_builder_build_count(builder);
881
882 if (!gom_command_execute(command, &cursor, &error)) {
883 g_simple_async_result_take_error(simple, error);
884 goto out;
885 }
886
887 if (!gom_cursor_next(cursor)) {
888 g_simple_async_result_set_error(simple, GOM_ERROR,
889 GOM_ERROR_RESOURCE_CURSOR,
890 "No result was returned from the cursor.");
891 goto out;
892 }
893
894 count = gom_cursor_get_column_int64(cursor, 0);
895 group = g_object_new(GOM_TYPE_RESOURCE_GROUP,
896 "count", count,
897 "filter", filter,
898 "m2m-table", m2m_table,
899 "m2m-type", G_TYPE_FROM_INSTANCE(resource),
900 "repository", repository,
901 "resource-type", resource_type,
902 NULL);
903
904 g_simple_async_result_set_op_res_gpointer(simple, group, g_object_unref);
905
906 out:
907 g_object_unref(resource);
908 g_clear_object(&command);
909 g_clear_object(&cursor);
910 g_clear_object(&builder);
911
912 g_simple_async_result_complete_in_idle(simple);
913 g_object_unref(simple);
914 }
915
916 void
gom_resource_fetch_m2m_async(GomResource * resource,GType resource_type,const gchar * m2m_table,GomFilter * filter,GAsyncReadyCallback callback,gpointer user_data)917 gom_resource_fetch_m2m_async (GomResource *resource,
918 GType resource_type,
919 const gchar *m2m_table,
920 GomFilter *filter,
921 GAsyncReadyCallback callback,
922 gpointer user_data)
923 {
924 GSimpleAsyncResult *simple;
925 GomRepository *repository;
926 GomAdapter *adapter;
927
928 g_return_if_fail(GOM_IS_RESOURCE(resource));
929 g_return_if_fail(g_type_is_a(resource_type, GOM_TYPE_RESOURCE));
930 g_return_if_fail(m2m_table != NULL);
931 g_return_if_fail(callback != NULL);
932
933 repository = gom_resource_get_repository(resource);
934 g_assert(GOM_IS_REPOSITORY(repository));
935
936 adapter = gom_repository_get_adapter(repository);
937 g_assert(GOM_IS_ADAPTER(adapter));
938
939 simple = g_simple_async_result_new(G_OBJECT(resource), callback, user_data,
940 gom_resource_fetch_m2m_async);
941 g_object_set_data(G_OBJECT(simple), "resource-type",
942 GINT_TO_POINTER(resource_type));
943 g_object_set_data_full(G_OBJECT(simple), "m2m-table",
944 g_strdup(m2m_table), g_free);
945 if (filter) {
946 g_object_set_data_full(G_OBJECT(simple), "filter",
947 g_object_ref(filter), g_object_unref);
948 }
949
950 gom_adapter_queue_read(adapter,
951 gom_resource_fetch_m2m_cb,
952 simple);
953 }
954
955 /**
956 * gom_resource_fetch_m2m_finish:
957 * @resource: (in): A #GomResource.
958 * @result: (in): A #GAsyncResult.
959 * @error: (out): A location for a #GError, or %NULL.
960 *
961 * Completes the asynchronous request to fetch a group of resources that
962 * are related to the resource through a many-to-many table.
963 *
964 * Returns: (transfer full): A #GomResourceGroup.
965 */
966 GomResourceGroup *
gom_resource_fetch_m2m_finish(GomResource * resource,GAsyncResult * result,GError ** error)967 gom_resource_fetch_m2m_finish (GomResource *resource,
968 GAsyncResult *result,
969 GError **error)
970 {
971 GSimpleAsyncResult *simple = (GSimpleAsyncResult *)result;
972 GomResourceGroup *group;
973
974 g_return_val_if_fail(GOM_IS_RESOURCE(resource), NULL);
975 g_return_val_if_fail(G_IS_SIMPLE_ASYNC_RESULT(result), NULL);
976
977 if (!(group = g_simple_async_result_get_op_res_gpointer(simple))) {
978 g_simple_async_result_propagate_error(simple, error);
979 }
980
981 return group ? g_object_ref(group) : NULL;
982 }
983
984 /**
985 * gom_resource_finalize:
986 * @object: (in): A #GomResource.
987 *
988 * Finalizer for a #GomResource instance. Frees any resources held by
989 * the instance.
990 */
991 static void
gom_resource_finalize(GObject * object)992 gom_resource_finalize (GObject *object)
993 {
994 GomResource *resource = (GomResource *) object;
995 gom_resource_set_repository(resource, NULL);
996 G_OBJECT_CLASS(gom_resource_parent_class)->finalize(object);
997 }
998
999 /**
1000 * gom_resource_get_property:
1001 * @object: (in): A #GObject.
1002 * @prop_id: (in): The property identifier.
1003 * @value: (out): The given property.
1004 * @pspec: (in): A #ParamSpec.
1005 *
1006 * Get a given #GObject property.
1007 */
1008 static void
gom_resource_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1009 gom_resource_get_property (GObject *object,
1010 guint prop_id,
1011 GValue *value,
1012 GParamSpec *pspec)
1013 {
1014 GomResource *resource = GOM_RESOURCE(object);
1015
1016 switch (prop_id) {
1017 case PROP_REPOSITORY:
1018 g_value_set_object(value, gom_resource_get_repository(resource));
1019 break;
1020 default:
1021 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
1022 }
1023 }
1024
1025 /**
1026 * gom_resource_set_property:
1027 * @object: (in): A #GObject.
1028 * @prop_id: (in): The property identifier.
1029 * @value: (in): The given property.
1030 * @pspec: (in): A #ParamSpec.
1031 *
1032 * Set a given #GObject property.
1033 */
1034 static void
gom_resource_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1035 gom_resource_set_property (GObject *object,
1036 guint prop_id,
1037 const GValue *value,
1038 GParamSpec *pspec)
1039 {
1040 GomResource *resource = GOM_RESOURCE(object);
1041
1042 switch (prop_id) {
1043 case PROP_REPOSITORY:
1044 gom_resource_set_repository(resource, g_value_get_object(value));
1045 break;
1046 default:
1047 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
1048 }
1049 }
1050
1051 static void
pkey_changed_cb(GObject * gobject,GParamSpec * pspec,gpointer user_data)1052 pkey_changed_cb (GObject *gobject,
1053 GParamSpec *pspec,
1054 gpointer user_data)
1055 {
1056 GomResource *resource = (GomResource *) gobject;
1057
1058 /* Did the developer reset the primary key? */
1059 if (!has_primary_key(GOM_RESOURCE(resource))) {
1060 resource->priv->is_from_table = FALSE;
1061 }
1062 }
1063
1064 static void
gom_resource_constructed(GObject * object)1065 gom_resource_constructed (GObject *object)
1066 {
1067 char *pkey_signal;
1068 GomResourceClass *klass;
1069
1070 /* Monitor the primary key */
1071 klass = GOM_RESOURCE_CLASS (G_OBJECT_GET_CLASS(object));
1072 g_assert (klass->primary_key[0] != '\0');
1073 pkey_signal = g_strdup_printf("notify::%s", klass->primary_key);
1074 g_signal_connect (G_OBJECT (object), pkey_signal,
1075 G_CALLBACK (pkey_changed_cb), NULL);
1076 g_free(pkey_signal);
1077
1078 G_OBJECT_CLASS (gom_resource_parent_class)->constructed (object);
1079 }
1080
1081 /**
1082 * gom_resource_class_init:
1083 * @klass: (in): A #GomResourceClass.
1084 *
1085 * Initializes the #GomResourceClass and prepares the vtable.
1086 */
1087 static void
gom_resource_class_init(GomResourceClass * klass)1088 gom_resource_class_init (GomResourceClass *klass)
1089 {
1090 GObjectClass *object_class;
1091
1092 object_class = G_OBJECT_CLASS(klass);
1093 object_class->finalize = gom_resource_finalize;
1094 object_class->get_property = gom_resource_get_property;
1095 object_class->set_property = gom_resource_set_property;
1096 object_class->constructed = gom_resource_constructed;
1097 g_type_class_add_private(object_class, sizeof(GomResourcePrivate));
1098
1099 gParamSpecs[PROP_REPOSITORY] =
1100 g_param_spec_object("repository",
1101 "Repository",
1102 "The resources repository.",
1103 GOM_TYPE_REPOSITORY,
1104 G_PARAM_READWRITE);
1105 g_object_class_install_property(object_class, PROP_REPOSITORY,
1106 gParamSpecs[PROP_REPOSITORY]);
1107 }
1108
1109 /**
1110 * gom_resource_init:
1111 * @resource: (in): A #GomResource.
1112 *
1113 * Initializes the newly created #GomResource instance.
1114 */
1115 static void
gom_resource_init(GomResource * resource)1116 gom_resource_init (GomResource *resource)
1117 {
1118 resource->priv =
1119 G_TYPE_INSTANCE_GET_PRIVATE(resource,
1120 GOM_TYPE_RESOURCE,
1121 GomResourcePrivate);
1122 }
1123
1124 gboolean
gom_resource_get_is_from_table(GomResource * resource)1125 gom_resource_get_is_from_table (GomResource *resource)
1126 {
1127 return resource->priv->is_from_table;
1128 }
1129
1130 void
gom_resource_set_is_from_table(GomResource * resource,gboolean is_from_table)1131 gom_resource_set_is_from_table (GomResource *resource,
1132 gboolean is_from_table)
1133 {
1134 resource->priv->is_from_table = is_from_table;
1135 }
1136
1137 GQuark
gom_resource_new_in_version_quark(void)1138 gom_resource_new_in_version_quark (void)
1139 {
1140 return g_quark_from_static_string("gom_resource_new_in_version_quark");
1141 }
1142
1143 GQuark
gom_resource_not_mapped_quark(void)1144 gom_resource_not_mapped_quark (void)
1145 {
1146 return g_quark_from_static_string("gom_resource_not_mapped_quark");
1147 }
1148
1149 GQuark
gom_resource_to_bytes_func_quark(void)1150 gom_resource_to_bytes_func_quark (void)
1151 {
1152 return g_quark_from_static_string("gom_resource_to_bytes_func_quark");
1153 }
1154
1155 GQuark
gom_resource_from_bytes_func_quark(void)1156 gom_resource_from_bytes_func_quark (void)
1157 {
1158 return g_quark_from_static_string("gom_resource_from_bytes_func_quark");
1159 }
1160
1161 GQuark
gom_resource_ref_table_class(void)1162 gom_resource_ref_table_class (void)
1163 {
1164 return g_quark_from_static_string("gom_resource_ref_table_class");
1165 }
1166
1167 GQuark
gom_resource_ref_property_name(void)1168 gom_resource_ref_property_name (void)
1169 {
1170 return g_quark_from_static_string("gom_resource_ref_property_name");
1171 }
1172
1173 GQuark
gom_resource_unique(void)1174 gom_resource_unique (void)
1175 {
1176 return g_quark_from_static_string("gom_resource_unique");
1177 }
1178
1179 GQuark
gom_resource_notnull(void)1180 gom_resource_notnull (void)
1181 {
1182 return g_quark_from_static_string("gom_resource_notnull");
1183 }
1184