Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-3.0-or-later
2 : : // SPDX-FileCopyrightText: Andy Holmes <andrew.g.r.holmes@gmail.com>
3 : :
4 : : #define G_LOG_DOMAIN "valent-contacts-adapter"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <gio/gio.h>
9 : : #include <libvalent-core.h>
10 : : #include <libtracker-sparql/tracker-sparql.h>
11 : :
12 : : #include "valent-contacts.h"
13 : : #include "valent-contact-list.h"
14 : :
15 : : #include "valent-contacts-adapter.h"
16 : :
17 : : #define GET_CONTACT_RQ "/ca/andyholmes/Valent/sparql/get-contact.rq"
18 : : #define GET_CONTACT_LIST_RQ "/ca/andyholmes/Valent/sparql/get-contact-list.rq"
19 : : #define GET_CONTACT_LISTS_RQ "/ca/andyholmes/Valent/sparql/get-contact-lists.rq"
20 : :
21 : :
22 : : /**
23 : : * ValentContactsAdapter:
24 : : *
25 : : * An abstract base class for address book providers.
26 : : *
27 : : * `ValentContactsAdapter` is a base class for plugins that provide an
28 : : * interface to address books and contacts. This usually means managing entries
29 : : * in the %VALENT_CONTACTS_GRAPH.
30 : : *
31 : : * ## `.plugin` File
32 : : *
33 : : * Implementations may define the following extra fields in the `.plugin` file:
34 : : *
35 : : * - `X-ContactsAdapterPriority`
36 : : *
37 : : * An integer indicating the adapter priority. The implementation with the
38 : : * lowest value will be used as the primary adapter.
39 : : *
40 : : * Since: 1.0
41 : : */
42 : :
43 : : typedef struct
44 : : {
45 : : TrackerSparqlConnection *connection;
46 : : TrackerNotifier *notifier;
47 : : TrackerSparqlStatement *get_contact_list_stmt;
48 : : TrackerSparqlStatement *get_contact_lists_stmt;
49 : : GRegex *iri_pattern;
50 : : char *iri;
51 : : GCancellable *cancellable;
52 : :
53 : : /* list model */
54 : : GPtrArray *items;
55 : : } ValentContactsAdapterPrivate;
56 : :
57 : : static void g_list_model_iface_init (GListModelInterface *iface);
58 : :
59 : : static void valent_contacts_adapter_add_contact_list (ValentContactsAdapter *self,
60 : : const char *iri);
61 : : static void valent_contacts_adapter_remove_contact_list (ValentContactsAdapter *self,
62 : : const char *iri);
63 : :
64 [ + + + - ]: 623420 : G_DEFINE_ABSTRACT_TYPE_WITH_CODE (ValentContactsAdapter, valent_contacts_adapter, VALENT_TYPE_EXTENSION,
65 : : G_ADD_PRIVATE (ValentContactsAdapter)
66 : : G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, g_list_model_iface_init))
67 : :
68 : : typedef enum
69 : : {
70 : : PROP_CONNECTION = 1,
71 : : } ValentContactsAdapterProperty;
72 : :
73 : : static GParamSpec *properties[PROP_CONNECTION + 1] = { NULL, };
74 : :
75 : : gboolean
76 : 21 : valent_contacts_adapter_event_is_contact_list (ValentContactsAdapter *self,
77 : : const char *iri)
78 : : {
79 : 21 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
80 : :
81 : 21 : return g_regex_match (priv->iri_pattern, iri, G_REGEX_MATCH_DEFAULT, NULL);
82 : : }
83 : :
84 : : static void
85 : 17 : on_notifier_event (TrackerNotifier *notifier,
86 : : const char *service,
87 : : const char *graph,
88 : : GPtrArray *events,
89 : : ValentContactsAdapter *self)
90 : : {
91 [ - + ]: 17 : g_assert (VALENT_IS_CONTACTS_ADAPTER (self));
92 : :
93 [ + - ]: 17 : if (g_strcmp0 (VALENT_CONTACTS_GRAPH, graph) != 0)
94 : : return;
95 : :
96 [ + + ]: 38 : for (unsigned int i = 0; i < events->len; i++)
97 : : {
98 : 21 : TrackerNotifierEvent *event = g_ptr_array_index (events, i);
99 : 21 : const char *urn = tracker_notifier_event_get_urn (event);
100 : :
101 [ + + ]: 21 : if (!valent_contacts_adapter_event_is_contact_list (self, urn))
102 : 5 : continue;
103 : :
104 [ + + - + ]: 16 : switch (tracker_notifier_event_get_event_type (event))
105 : : {
106 : 14 : case TRACKER_NOTIFIER_EVENT_CREATE:
107 : 14 : VALENT_NOTE ("CREATE: %s", urn);
108 : 14 : valent_contacts_adapter_add_contact_list (self, urn);
109 : 14 : break;
110 : :
111 : 1 : case TRACKER_NOTIFIER_EVENT_DELETE:
112 : 1 : VALENT_NOTE ("DELETE: %s", urn);
113 : 1 : valent_contacts_adapter_remove_contact_list (self, urn);
114 : 1 : break;
115 : :
116 : : case TRACKER_NOTIFIER_EVENT_UPDATE:
117 : : VALENT_NOTE ("UPDATE: %s", urn);
118 : : // valent_contacts_adapter_update_contact_list (self, urn);
119 : : break;
120 : :
121 : 0 : default:
122 : 21 : g_warn_if_reached ();
123 : : }
124 : : }
125 : : }
126 : :
127 : : static ValentContactList *
128 : 1 : valent_contact_list_from_sparql_cursor (ValentContactsAdapter *self,
129 : : TrackerSparqlCursor *cursor)
130 : : {
131 : 1 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
132 : 1 : const char *iri = NULL;
133 : :
134 [ + - ]: 1 : if (!tracker_sparql_cursor_is_bound (cursor, 0))
135 : : return NULL;
136 : :
137 : 1 : iri = tracker_sparql_cursor_get_string (cursor, 0, NULL);
138 : 1 : return g_object_new (VALENT_TYPE_CONTACT_LIST,
139 : : "connection", tracker_sparql_cursor_get_connection (cursor),
140 : : "notifier", priv->notifier,
141 : : "iri", iri,
142 : : NULL);
143 : : }
144 : :
145 : : static void
146 : 20 : cursor_get_contact_lists_cb (TrackerSparqlCursor *cursor,
147 : : GAsyncResult *result,
148 : : gpointer user_data)
149 : : {
150 : 20 : g_autoptr (ValentContactsAdapter) self = VALENT_CONTACTS_ADAPTER (g_steal_pointer (&user_data));
151 : 20 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
152 [ + - ]: 20 : g_autoptr (GError) error = NULL;
153 : :
154 [ + + ]: 20 : if (tracker_sparql_cursor_next_finish (cursor, result, &error))
155 : : {
156 : 1 : ValentContactList *contacts = NULL;
157 : :
158 : 1 : contacts = valent_contact_list_from_sparql_cursor (self, cursor);
159 [ + - ]: 1 : if (contacts != NULL)
160 : : {
161 : 1 : unsigned int position;
162 : :
163 : 1 : position = priv->items->len;
164 : 1 : g_ptr_array_add (priv->items, g_object_ref (contacts));
165 : 1 : g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
166 : : }
167 : :
168 : 1 : tracker_sparql_cursor_next_async (cursor,
169 : : g_task_get_cancellable (G_TASK (result)),
170 : : (GAsyncReadyCallback) cursor_get_contact_lists_cb,
171 : : g_object_ref (self));
172 : : }
173 : : else
174 : : {
175 [ - + - - ]: 19 : if (error != NULL && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
176 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
177 : :
178 : 19 : tracker_sparql_cursor_close (cursor);
179 : : }
180 : 20 : }
181 : :
182 : : static void
183 : 19 : execute_get_contact_lists_cb (TrackerSparqlStatement *stmt,
184 : : GAsyncResult *result,
185 : : gpointer user_data)
186 : : {
187 : 38 : g_autoptr (ValentContactsAdapter) self = VALENT_CONTACTS_ADAPTER (g_steal_pointer (&user_data));
188 [ - - + - ]: 19 : g_autoptr (TrackerSparqlCursor) cursor = NULL;
189 [ - - ]: 19 : g_autoptr (GError) error = NULL;
190 : :
191 : 19 : cursor = tracker_sparql_statement_execute_finish (stmt, result, &error);
192 [ - + ]: 19 : if (cursor == NULL)
193 : : {
194 [ # # ]: 0 : if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
195 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
196 : :
197 [ # # ]: 0 : return;
198 : : }
199 : :
200 [ - + ]: 19 : tracker_sparql_cursor_next_async (cursor,
201 : : g_task_get_cancellable (G_TASK (result)),
202 : : (GAsyncReadyCallback) cursor_get_contact_lists_cb,
203 : : g_object_ref (self));
204 : : }
205 : :
206 : : static void
207 : 19 : valent_contacts_adapter_load_contact_lists (ValentContactsAdapter *self)
208 : : {
209 : 19 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
210 : 19 : g_autoptr (GError) error = NULL;
211 : :
212 [ - + ]: 19 : g_assert (VALENT_IS_CONTACTS_ADAPTER (self));
213 [ + - ]: 19 : g_return_if_fail (TRACKER_IS_SPARQL_CONNECTION (priv->connection));
214 : :
215 [ + - ]: 19 : if (priv->cancellable != NULL)
216 : : return;
217 : :
218 : 19 : priv->cancellable = valent_object_ref_cancellable (VALENT_OBJECT (self));
219 [ + - ]: 19 : if (priv->get_contact_lists_stmt == NULL)
220 : : {
221 : 19 : priv->get_contact_lists_stmt =
222 : 19 : tracker_sparql_connection_load_statement_from_gresource (priv->connection,
223 : : GET_CONTACT_LISTS_RQ,
224 : : priv->cancellable,
225 : : &error);
226 : : }
227 : :
228 [ - + ]: 19 : if (priv->get_contact_lists_stmt == NULL)
229 : : {
230 [ # # # # ]: 0 : if (error != NULL && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
231 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
232 : :
233 : 0 : return;
234 : : }
235 : :
236 [ - + ]: 19 : tracker_sparql_statement_execute_async (priv->get_contact_lists_stmt,
237 : : priv->cancellable,
238 : : (GAsyncReadyCallback) execute_get_contact_lists_cb,
239 : : g_object_ref (self));
240 : : }
241 : :
242 : : static void
243 : 14 : valent_contacts_adapter_add_contact_list (ValentContactsAdapter *self,
244 : : const char *iri)
245 : : {
246 : 14 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
247 : 28 : g_autoptr (GListModel) list = NULL;
248 : 14 : unsigned int position = 0;
249 : :
250 [ - + ]: 14 : g_assert (VALENT_IS_CONTACTS_ADAPTER (self));
251 [ + - ]: 14 : g_assert (TRACKER_IS_SPARQL_CONNECTION (priv->connection));
252 : :
253 : 14 : list = g_object_new (VALENT_TYPE_CONTACT_LIST,
254 : : "connection", priv->connection,
255 : : "notifier", priv->notifier,
256 : : "iri", iri,
257 : : NULL);
258 : :
259 : 14 : position = priv->items->len;
260 : 14 : g_ptr_array_add (priv->items, g_steal_pointer (&list));
261 : 14 : g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
262 : 14 : }
263 : :
264 : : static inline gboolean
265 : 2 : find_item (gconstpointer a,
266 : : gconstpointer b)
267 : : {
268 : 2 : const char *iri = valent_object_get_iri ((ValentObject *)a);
269 : :
270 : 2 : return g_strcmp0 (iri, (const char *)b) == 0;
271 : : }
272 : :
273 : : static void
274 : 1 : valent_contacts_adapter_remove_contact_list (ValentContactsAdapter *self,
275 : : const char *iri)
276 : : {
277 : 1 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
278 : 1 : unsigned int position = 0;
279 : :
280 [ - + ]: 1 : g_assert (VALENT_IS_CONTACTS_ADAPTER (self));
281 : :
282 [ - + ]: 1 : if (!g_ptr_array_find_with_equal_func (priv->items, iri, find_item, &position))
283 : : {
284 : 0 : g_warning ("No such store \"%s\" found in \"%s\"",
285 : : iri,
286 : : G_OBJECT_TYPE_NAME (self));
287 : 0 : return;
288 : : }
289 : :
290 : 1 : g_ptr_array_remove_index (priv->items, position);
291 : 1 : g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 0);
292 : : }
293 : :
294 : : static gboolean
295 : 19 : valent_contacts_adapter_open (ValentContactsAdapter *self,
296 : : GError **error)
297 : : {
298 : 19 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
299 : 19 : ValentContext *context = NULL;
300 : 38 : g_autoptr (GFile) file = NULL;
301 [ + - ]: 19 : g_autoptr (GFile) ontology = NULL;
302 : 19 : const char *iri = NULL;
303 [ + - ]: 19 : g_autofree char *iri_pattern = NULL;
304 : :
305 : 19 : context = valent_extension_get_context (VALENT_EXTENSION (self));
306 : 19 : file = valent_context_get_cache_file (context, "metadata");
307 : 19 : ontology = g_file_new_for_uri ("resource:///ca/andyholmes/Valent/ontologies/");
308 : :
309 : 38 : priv->connection =
310 : 19 : tracker_sparql_connection_new (TRACKER_SPARQL_CONNECTION_FLAGS_NONE,
311 : : file,
312 : : ontology,
313 : : NULL,
314 : : error);
315 : :
316 [ + - ]: 19 : if (priv->connection == NULL)
317 : : return FALSE;
318 : :
319 : 19 : iri = valent_object_get_iri (VALENT_OBJECT (self));
320 : 19 : iri_pattern = g_strdup_printf ("^%s:([^:]+)$", iri);
321 : 19 : priv->iri_pattern = g_regex_new (iri_pattern,
322 : : G_REGEX_OPTIMIZE,
323 : : G_REGEX_MATCH_DEFAULT,
324 : : NULL);
325 : :
326 : 19 : priv->notifier = tracker_sparql_connection_create_notifier (priv->connection);
327 : 19 : g_signal_connect_object (priv->notifier,
328 : : "events",
329 : : G_CALLBACK (on_notifier_event),
330 : : self,
331 : : G_CONNECT_DEFAULT);
332 : :
333 : 19 : return TRUE;
334 : : }
335 : :
336 : : /*
337 : : * GListModel
338 : : */
339 : : static gpointer
340 : 20 : valent_contacts_adapter_get_item (GListModel *list,
341 : : unsigned int position)
342 : : {
343 : 20 : ValentContactsAdapter *self = VALENT_CONTACTS_ADAPTER (list);
344 : 20 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
345 : :
346 [ - + ]: 20 : g_assert (VALENT_IS_CONTACTS_ADAPTER (self));
347 : :
348 [ + + ]: 20 : if G_UNLIKELY (position >= priv->items->len)
349 : : return NULL;
350 : :
351 : 19 : return g_object_ref (g_ptr_array_index (priv->items, position));
352 : : }
353 : :
354 : : static GType
355 : 1 : valent_contacts_adapter_get_item_type (GListModel *list)
356 : : {
357 : 1 : return G_TYPE_LIST_MODEL;
358 : : }
359 : :
360 : : static unsigned int
361 : 311400 : valent_contacts_adapter_get_n_items (GListModel *list)
362 : : {
363 : 311400 : ValentContactsAdapter *self = VALENT_CONTACTS_ADAPTER (list);
364 : 311400 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
365 : :
366 [ - + ]: 311400 : g_assert (VALENT_IS_CONTACTS_ADAPTER (self));
367 : :
368 : 311400 : return priv->items->len;
369 : : }
370 : :
371 : : static void
372 : 53 : g_list_model_iface_init (GListModelInterface *iface)
373 : : {
374 : 53 : iface->get_item = valent_contacts_adapter_get_item;
375 : 53 : iface->get_item_type = valent_contacts_adapter_get_item_type;
376 : 53 : iface->get_n_items = valent_contacts_adapter_get_n_items;
377 : 53 : }
378 : :
379 : : /*
380 : : * ValentObject
381 : : */
382 : : static void
383 : 28 : valent_contacts_adapter_destroy (ValentObject *object)
384 : : {
385 : 28 : ValentContactsAdapter *self = VALENT_CONTACTS_ADAPTER (object);
386 : 28 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
387 : :
388 [ + + ]: 28 : g_clear_object (&priv->notifier);
389 [ - + ]: 28 : g_clear_object (&priv->get_contact_list_stmt);
390 [ + + ]: 28 : g_clear_object (&priv->get_contact_lists_stmt);
391 [ + + ]: 28 : g_clear_pointer (&priv->iri_pattern, g_regex_unref);
392 [ + + ]: 28 : g_clear_object (&priv->cancellable);
393 : :
394 [ + + ]: 28 : if (priv->connection != NULL)
395 : : {
396 : 15 : tracker_sparql_connection_close (priv->connection);
397 [ + - ]: 15 : g_clear_object (&priv->connection);
398 : : }
399 : :
400 : 28 : VALENT_OBJECT_CLASS (valent_contacts_adapter_parent_class)->destroy (object);
401 : 28 : }
402 : :
403 : : /*
404 : : * GObject
405 : : */
406 : : static void
407 : 19 : valent_contacts_adapter_constructed (GObject *object)
408 : : {
409 : 19 : ValentContactsAdapter *self = VALENT_CONTACTS_ADAPTER (object);
410 : 38 : g_autoptr (GError) error = NULL;
411 : :
412 : 19 : G_OBJECT_CLASS (valent_contacts_adapter_parent_class)->constructed (object);
413 : :
414 [ + - ]: 19 : if (valent_contacts_adapter_open (self, &error))
415 : 19 : valent_contacts_adapter_load_contact_lists (self);
416 : : else
417 : 0 : g_critical ("%s(): %s", G_STRFUNC, error->message);
418 : 19 : }
419 : :
420 : : static void
421 : 15 : valent_contacts_adapter_finalize (GObject *object)
422 : : {
423 : 15 : ValentContactsAdapter *self = VALENT_CONTACTS_ADAPTER (object);
424 : 15 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
425 : :
426 [ + - ]: 15 : g_clear_pointer (&priv->items, g_ptr_array_unref);
427 [ - + ]: 15 : g_clear_object (&priv->cancellable);
428 : :
429 : 15 : G_OBJECT_CLASS (valent_contacts_adapter_parent_class)->finalize (object);
430 : 15 : }
431 : :
432 : : static void
433 : 19 : valent_contacts_adapter_get_property (GObject *object,
434 : : guint prop_id,
435 : : GValue *value,
436 : : GParamSpec *pspec)
437 : : {
438 : 19 : ValentContactsAdapter *self = VALENT_CONTACTS_ADAPTER (object);
439 : 19 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
440 : :
441 [ + - ]: 19 : switch ((ValentContactsAdapterProperty)prop_id)
442 : : {
443 : 19 : case PROP_CONNECTION:
444 : 19 : g_value_set_object (value, priv->connection);
445 : 19 : break;
446 : :
447 : 0 : default:
448 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
449 : : }
450 : 19 : }
451 : :
452 : : static void
453 : 19 : valent_contacts_adapter_set_property (GObject *object,
454 : : guint prop_id,
455 : : const GValue *value,
456 : : GParamSpec *pspec)
457 : : {
458 : 19 : ValentContactsAdapter *self = VALENT_CONTACTS_ADAPTER (object);
459 : 19 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
460 : :
461 [ + - ]: 19 : switch ((ValentContactsAdapterProperty)prop_id)
462 : : {
463 : 19 : case PROP_CONNECTION:
464 : 19 : g_set_object (&priv->connection, g_value_get_object (value));
465 : 19 : break;
466 : :
467 : 0 : default:
468 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
469 : : }
470 : 19 : }
471 : :
472 : : static void
473 : 53 : valent_contacts_adapter_class_init (ValentContactsAdapterClass *klass)
474 : : {
475 : 53 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
476 : 53 : ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
477 : :
478 : 53 : object_class->constructed = valent_contacts_adapter_constructed;
479 : 53 : object_class->finalize = valent_contacts_adapter_finalize;
480 : 53 : object_class->get_property = valent_contacts_adapter_get_property;
481 : 53 : object_class->set_property = valent_contacts_adapter_set_property;
482 : :
483 : 53 : vobject_class->destroy = valent_contacts_adapter_destroy;
484 : :
485 : : /**
486 : : * ValentContactsAdapter:connection:
487 : : *
488 : : * The database connection.
489 : : */
490 : 106 : properties [PROP_CONNECTION] =
491 : 53 : g_param_spec_object ("connection", NULL, NULL,
492 : : TRACKER_TYPE_SPARQL_CONNECTION,
493 : : (G_PARAM_READWRITE |
494 : : G_PARAM_CONSTRUCT_ONLY |
495 : : G_PARAM_STATIC_STRINGS));
496 : :
497 : 53 : g_object_class_install_properties (object_class, G_N_ELEMENTS (properties), properties);
498 : 53 : }
499 : :
500 : : static void
501 : 19 : valent_contacts_adapter_init (ValentContactsAdapter *adapter)
502 : : {
503 : 19 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (adapter);
504 : :
505 : 19 : priv->items = g_ptr_array_new_with_free_func (g_object_unref);
506 : 19 : }
507 : :
|