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 [ + + + - ]: 434662 : 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] = { 0, };
74 : :
75 : : gboolean
76 : 18 : valent_contacts_adapter_event_is_contact_list (ValentContactsAdapter *self,
77 : : const char *iri)
78 : : {
79 : 18 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
80 : :
81 : 18 : return g_regex_match (priv->iri_pattern, iri, G_REGEX_MATCH_DEFAULT, NULL);
82 : : }
83 : :
84 : : static void
85 : 14 : on_notifier_event (TrackerNotifier *notifier,
86 : : const char *service,
87 : : const char *graph,
88 : : GPtrArray *events,
89 : : ValentContactsAdapter *self)
90 : : {
91 [ + - ]: 14 : g_assert (VALENT_IS_CONTACTS_ADAPTER (self));
92 : :
93 [ + - ]: 14 : if (g_strcmp0 (VALENT_CONTACTS_GRAPH, graph) != 0)
94 : : return;
95 : :
96 [ + + ]: 32 : for (unsigned int i = 0; i < events->len; i++)
97 : : {
98 : 18 : TrackerNotifierEvent *event = g_ptr_array_index (events, i);
99 : 18 : const char *urn = tracker_notifier_event_get_urn (event);
100 : :
101 [ + + ]: 18 : if (!valent_contacts_adapter_event_is_contact_list (self, urn))
102 : 5 : continue;
103 : :
104 [ + + - + ]: 13 : switch (tracker_notifier_event_get_event_type (event))
105 : : {
106 : 11 : case TRACKER_NOTIFIER_EVENT_CREATE:
107 : 11 : VALENT_NOTE ("CREATE: %s", urn);
108 : 11 : valent_contacts_adapter_add_contact_list (self, urn);
109 : 11 : 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 : 18 : g_warn_if_reached ();
123 : : }
124 : : }
125 : : }
126 : :
127 : : static ValentContactList *
128 : 0 : valent_contact_list_from_sparql_cursor (ValentContactsAdapter *self,
129 : : TrackerSparqlCursor *cursor)
130 : : {
131 : 0 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
132 : 0 : const char *iri = NULL;
133 : :
134 [ # # ]: 0 : if (!tracker_sparql_cursor_is_bound (cursor, 0))
135 : : return NULL;
136 : :
137 : 0 : iri = tracker_sparql_cursor_get_string (cursor, 0, NULL);
138 : 0 : 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 : 11 : cursor_get_contact_lists_cb (TrackerSparqlCursor *cursor,
147 : : GAsyncResult *result,
148 : : gpointer user_data)
149 : : {
150 : 11 : g_autoptr (ValentContactsAdapter) self = VALENT_CONTACTS_ADAPTER (g_steal_pointer (&user_data));
151 : 11 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
152 [ + - ]: 11 : g_autoptr (GError) error = NULL;
153 : :
154 [ - + ]: 11 : if (tracker_sparql_cursor_next_finish (cursor, result, &error))
155 : : {
156 : 0 : ValentContactList *contacts = NULL;
157 : :
158 : 0 : contacts = valent_contact_list_from_sparql_cursor (self, cursor);
159 [ # # ]: 0 : if (contacts != NULL)
160 : : {
161 : 0 : unsigned int position;
162 : :
163 : 0 : position = priv->items->len;
164 : 0 : g_ptr_array_add (priv->items, g_object_ref (contacts));
165 : 0 : g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
166 : : }
167 : :
168 : 0 : 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 [ - + - - ]: 11 : 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 : 11 : tracker_sparql_cursor_close (cursor);
179 : : }
180 : 11 : }
181 : :
182 : : static void
183 : 14 : execute_get_contact_lists_cb (TrackerSparqlStatement *stmt,
184 : : GAsyncResult *result,
185 : : gpointer user_data)
186 : : {
187 : 28 : g_autoptr (ValentContactsAdapter) self = VALENT_CONTACTS_ADAPTER (g_steal_pointer (&user_data));
188 [ + - + - ]: 14 : g_autoptr (TrackerSparqlCursor) cursor = NULL;
189 [ + - ]: 14 : g_autoptr (GError) error = NULL;
190 : :
191 : 14 : cursor = tracker_sparql_statement_execute_finish (stmt, result, &error);
192 [ + + ]: 14 : if (cursor == NULL)
193 : : {
194 [ - + ]: 3 : if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
195 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
196 : :
197 [ + - ]: 3 : return;
198 : : }
199 : :
200 [ - + ]: 11 : 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 : 14 : valent_contacts_adapter_load_contact_lists (ValentContactsAdapter *self)
208 : : {
209 : 14 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
210 : 14 : g_autoptr (GError) error = NULL;
211 : :
212 [ + - ]: 14 : g_assert (VALENT_IS_CONTACTS_ADAPTER (self));
213 [ - + ]: 14 : g_return_if_fail (TRACKER_IS_SPARQL_CONNECTION (priv->connection));
214 : :
215 [ + - ]: 14 : if (priv->cancellable != NULL)
216 : : return;
217 : :
218 : 14 : priv->cancellable = valent_object_ref_cancellable (VALENT_OBJECT (self));
219 [ + - ]: 14 : if (priv->get_contact_lists_stmt == NULL)
220 : : {
221 : 14 : priv->get_contact_lists_stmt =
222 : 14 : tracker_sparql_connection_load_statement_from_gresource (priv->connection,
223 : : GET_CONTACT_LISTS_RQ,
224 : : priv->cancellable,
225 : : &error);
226 : : }
227 : :
228 [ - + ]: 14 : 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 [ - + ]: 14 : 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 : 11 : valent_contacts_adapter_add_contact_list (ValentContactsAdapter *self,
244 : : const char *iri)
245 : : {
246 : 11 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
247 : 22 : g_autoptr (GListModel) list = NULL;
248 : 11 : unsigned int position = 0;
249 : :
250 [ + - ]: 11 : g_assert (VALENT_IS_CONTACTS_ADAPTER (self));
251 [ - + ]: 11 : g_assert (TRACKER_IS_SPARQL_CONNECTION (priv->connection));
252 : :
253 : 11 : list = g_object_new (VALENT_TYPE_CONTACT_LIST,
254 : : "connection", priv->connection,
255 : : "notifier", priv->notifier,
256 : : "iri", iri,
257 : : NULL);
258 : :
259 : 11 : position = priv->items->len;
260 : 11 : g_ptr_array_add (priv->items, g_steal_pointer (&list));
261 : 11 : g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
262 : 11 : }
263 : :
264 : : static inline gboolean
265 : 2 : find_item (gconstpointer a,
266 : : gconstpointer b)
267 : : {
268 : 2 : g_autofree char *iri = valent_object_dup_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 : g_autoptr (GListModel) item = NULL;
279 : 1 : unsigned int position = 0;
280 : :
281 [ + - ]: 1 : g_assert (VALENT_IS_CONTACTS_ADAPTER (self));
282 : :
283 [ - + ]: 1 : if (!g_ptr_array_find_with_equal_func (priv->items, iri, find_item, &position))
284 : : {
285 : 0 : g_warning ("No such store \"%s\" found in \"%s\"",
286 : : iri,
287 : : G_OBJECT_TYPE_NAME (self));
288 : 0 : return;
289 : : }
290 : :
291 : 1 : item = g_ptr_array_steal_index (priv->items, position);
292 [ + - ]: 1 : g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 0);
293 : : }
294 : :
295 : : static gboolean
296 : 14 : valent_contacts_adapter_open (ValentContactsAdapter *self,
297 : : GError **error)
298 : : {
299 : 14 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
300 : 14 : ValentContext *context = NULL;
301 : 28 : g_autoptr (GFile) file = NULL;
302 [ + - ]: 14 : g_autoptr (GFile) ontology = NULL;
303 [ + - ]: 14 : g_autofree char *iri = NULL;
304 : 14 : g_autofree char *iri_pattern = NULL;
305 : :
306 : 14 : context = valent_extension_get_context (VALENT_EXTENSION (self));
307 : 14 : file = valent_context_get_cache_file (context, "metadata");
308 : 14 : ontology = g_file_new_for_uri ("resource:///ca/andyholmes/Valent/ontologies/");
309 : :
310 : 28 : priv->connection =
311 : 14 : tracker_sparql_connection_new (TRACKER_SPARQL_CONNECTION_FLAGS_NONE,
312 : : file,
313 : : ontology,
314 : : NULL,
315 : : error);
316 : :
317 [ + - ]: 14 : if (priv->connection == NULL)
318 : : return FALSE;
319 : :
320 : 14 : iri = valent_object_dup_iri (VALENT_OBJECT (self));
321 : 14 : iri_pattern = g_strdup_printf ("^%s:([^:]+)$", iri);
322 : 14 : priv->iri_pattern = g_regex_new (iri_pattern,
323 : : G_REGEX_OPTIMIZE,
324 : : G_REGEX_MATCH_DEFAULT,
325 : : NULL);
326 : :
327 : 14 : priv->notifier = tracker_sparql_connection_create_notifier (priv->connection);
328 : 14 : g_signal_connect_object (priv->notifier,
329 : : "events",
330 : : G_CALLBACK (on_notifier_event),
331 : : self,
332 : : G_CONNECT_DEFAULT);
333 : :
334 : 14 : return TRUE;
335 : : }
336 : :
337 : : /*
338 : : * GListModel
339 : : */
340 : : static gpointer
341 : 16 : valent_contacts_adapter_get_item (GListModel *list,
342 : : unsigned int position)
343 : : {
344 : 16 : ValentContactsAdapter *self = VALENT_CONTACTS_ADAPTER (list);
345 : 16 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
346 : :
347 [ + - ]: 16 : g_assert (VALENT_IS_CONTACTS_ADAPTER (self));
348 : :
349 [ + + ]: 16 : if G_UNLIKELY (position >= priv->items->len)
350 : : return NULL;
351 : :
352 : 15 : return g_object_ref (g_ptr_array_index (priv->items, position));
353 : : }
354 : :
355 : : static GType
356 : 1 : valent_contacts_adapter_get_item_type (GListModel *list)
357 : : {
358 : 1 : return G_TYPE_LIST_MODEL;
359 : : }
360 : :
361 : : static unsigned int
362 : 217041 : valent_contacts_adapter_get_n_items (GListModel *list)
363 : : {
364 : 217041 : ValentContactsAdapter *self = VALENT_CONTACTS_ADAPTER (list);
365 : 217041 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
366 : :
367 [ + - ]: 217041 : g_assert (VALENT_IS_CONTACTS_ADAPTER (self));
368 : :
369 : 217041 : return priv->items->len;
370 : : }
371 : :
372 : : static void
373 : 58 : g_list_model_iface_init (GListModelInterface *iface)
374 : : {
375 : 58 : iface->get_item = valent_contacts_adapter_get_item;
376 : 58 : iface->get_item_type = valent_contacts_adapter_get_item_type;
377 : 58 : iface->get_n_items = valent_contacts_adapter_get_n_items;
378 : 58 : }
379 : :
380 : : /*
381 : : * ValentObject
382 : : */
383 : : static void
384 : 17 : valent_contacts_adapter_destroy (ValentObject *object)
385 : : {
386 : 17 : ValentContactsAdapter *self = VALENT_CONTACTS_ADAPTER (object);
387 : 17 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
388 : :
389 [ + + ]: 17 : g_clear_object (&priv->notifier);
390 [ - + ]: 17 : g_clear_object (&priv->get_contact_list_stmt);
391 [ + + ]: 17 : g_clear_object (&priv->get_contact_lists_stmt);
392 [ + + ]: 17 : g_clear_pointer (&priv->iri_pattern, g_regex_unref);
393 [ + + ]: 17 : g_clear_object (&priv->cancellable);
394 : :
395 [ + + ]: 17 : if (priv->connection != NULL)
396 : : {
397 : 12 : tracker_sparql_connection_close (priv->connection);
398 [ + - ]: 12 : g_clear_object (&priv->connection);
399 : : }
400 : :
401 : 17 : VALENT_OBJECT_CLASS (valent_contacts_adapter_parent_class)->destroy (object);
402 : 17 : }
403 : :
404 : : /*
405 : : * GObject
406 : : */
407 : : static void
408 : 14 : valent_contacts_adapter_constructed (GObject *object)
409 : : {
410 : 14 : ValentContactsAdapter *self = VALENT_CONTACTS_ADAPTER (object);
411 : 28 : g_autoptr (GError) error = NULL;
412 : :
413 : 14 : G_OBJECT_CLASS (valent_contacts_adapter_parent_class)->constructed (object);
414 : :
415 [ + - ]: 14 : if (valent_contacts_adapter_open (self, &error))
416 : 14 : valent_contacts_adapter_load_contact_lists (self);
417 : : else
418 : 0 : g_critical ("%s(): %s", G_STRFUNC, error->message);
419 : 14 : }
420 : :
421 : : static void
422 : 12 : valent_contacts_adapter_finalize (GObject *object)
423 : : {
424 : 12 : ValentContactsAdapter *self = VALENT_CONTACTS_ADAPTER (object);
425 : 12 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
426 : :
427 [ + - ]: 12 : g_clear_pointer (&priv->items, g_ptr_array_unref);
428 [ - + ]: 12 : g_clear_object (&priv->cancellable);
429 : :
430 : 12 : G_OBJECT_CLASS (valent_contacts_adapter_parent_class)->finalize (object);
431 : 12 : }
432 : :
433 : : static void
434 : 15 : valent_contacts_adapter_get_property (GObject *object,
435 : : guint prop_id,
436 : : GValue *value,
437 : : GParamSpec *pspec)
438 : : {
439 : 15 : ValentContactsAdapter *self = VALENT_CONTACTS_ADAPTER (object);
440 : 15 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
441 : :
442 [ + - ]: 15 : switch ((ValentContactsAdapterProperty)prop_id)
443 : : {
444 : 15 : case PROP_CONNECTION:
445 : 15 : g_value_set_object (value, priv->connection);
446 : 15 : break;
447 : :
448 : 0 : default:
449 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
450 : : }
451 : 15 : }
452 : :
453 : : static void
454 : 14 : valent_contacts_adapter_set_property (GObject *object,
455 : : guint prop_id,
456 : : const GValue *value,
457 : : GParamSpec *pspec)
458 : : {
459 : 14 : ValentContactsAdapter *self = VALENT_CONTACTS_ADAPTER (object);
460 : 14 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (self);
461 : :
462 [ + - ]: 14 : switch ((ValentContactsAdapterProperty)prop_id)
463 : : {
464 : 14 : case PROP_CONNECTION:
465 : 14 : g_set_object (&priv->connection, g_value_get_object (value));
466 : 14 : break;
467 : :
468 : 0 : default:
469 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
470 : : }
471 : 14 : }
472 : :
473 : : static void
474 : 58 : valent_contacts_adapter_class_init (ValentContactsAdapterClass *klass)
475 : : {
476 : 58 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
477 : 58 : ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
478 : :
479 : 58 : object_class->constructed = valent_contacts_adapter_constructed;
480 : 58 : object_class->finalize = valent_contacts_adapter_finalize;
481 : 58 : object_class->get_property = valent_contacts_adapter_get_property;
482 : 58 : object_class->set_property = valent_contacts_adapter_set_property;
483 : :
484 : 58 : vobject_class->destroy = valent_contacts_adapter_destroy;
485 : :
486 : : /**
487 : : * ValentContactsAdapter:connection:
488 : : *
489 : : * The database connection.
490 : : */
491 : 116 : properties [PROP_CONNECTION] =
492 : 58 : g_param_spec_object ("connection", NULL, NULL,
493 : : TRACKER_TYPE_SPARQL_CONNECTION,
494 : : (G_PARAM_READWRITE |
495 : : G_PARAM_CONSTRUCT_ONLY |
496 : : G_PARAM_STATIC_STRINGS));
497 : :
498 : 58 : g_object_class_install_properties (object_class, G_N_ELEMENTS (properties), properties);
499 : 58 : }
500 : :
501 : : static void
502 : 14 : valent_contacts_adapter_init (ValentContactsAdapter *adapter)
503 : : {
504 : 14 : ValentContactsAdapterPrivate *priv = valent_contacts_adapter_get_instance_private (adapter);
505 : :
506 : 14 : priv->items = g_ptr_array_new_with_free_func (g_object_unref);
507 : 14 : }
508 : :
|