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