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-device"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <inttypes.h>
9 : :
10 : : #include <gio/gio.h>
11 : : #include <libebook-contacts/libebook-contacts.h>
12 : : #include <libtracker-sparql/tracker-sparql.h>
13 : : #include <valent.h>
14 : :
15 : : #include "valent-contacts-device.h"
16 : :
17 : : struct _ValentContactsDevice
18 : : {
19 : : ValentContactsAdapter parent_instance;
20 : :
21 : : ValentDevice *device;
22 : : GCancellable *cancellable;
23 : :
24 : : char *default_iri;
25 : : TrackerSparqlStatement *get_timestamp_stmt;
26 : : };
27 : :
28 [ + + + - ]: 17 : G_DEFINE_FINAL_TYPE (ValentContactsDevice, valent_contacts_device, VALENT_TYPE_CONTACTS_ADAPTER)
29 : :
30 : : static void
31 : 1 : execute_add_contacts_cb (TrackerSparqlConnection *connection,
32 : : GAsyncResult *result,
33 : : gpointer user_data)
34 : : {
35 : 2 : g_autoptr (GError) error = NULL;
36 : :
37 [ - + - - ]: 1 : if (!tracker_sparql_connection_update_resource_finish (connection, result, &error) &&
38 : 0 : !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
39 : : {
40 : 0 : g_debug ("%s(): %s", G_STRFUNC, error->message);
41 : : }
42 : 1 : }
43 : :
44 : : /*
45 : : * ValentContactsAdapter
46 : : */
47 : : static void
48 : 5 : valent_contacts_device_send_packet_cb (ValentDevice *device,
49 : : GAsyncResult *result,
50 : : gpointer user_data)
51 : : {
52 : 10 : g_autoptr (GError) error = NULL;
53 : :
54 [ + + ]: 5 : if (!valent_device_send_packet_finish (device, result, &error))
55 : : {
56 [ - + ]: 1 : if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED))
57 : 0 : g_critical ("%s(): %s", G_STRFUNC, error->message);
58 [ - + ]: 1 : else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_CONNECTED))
59 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
60 [ + - ]: 1 : else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
61 : 1 : g_debug ("%s(): %s", G_STRFUNC, error->message);
62 : : }
63 : 5 : }
64 : :
65 : : static void
66 : 1 : valent_contacts_device_handle_response_uids_timestamps (ValentContactsDevice *self,
67 : : JsonNode *packet)
68 : : {
69 : 2 : g_autoptr (JsonNode) request = NULL;
70 [ + - ]: 1 : g_autoptr (JsonBuilder) builder = NULL;
71 : 1 : JsonObjectIter iter;
72 : 1 : const char *uid;
73 : 1 : JsonNode *node;
74 : 1 : unsigned int n_requested = 0;
75 : :
76 : 1 : VALENT_ENTRY;
77 : :
78 [ + - ]: 1 : g_assert (VALENT_IS_CONTACTS_DEVICE (self));
79 : :
80 : 1 : valent_packet_init (&builder, "kdeconnect.contacts.request_vcards_by_uid");
81 : 1 : json_builder_set_member_name (builder, "uids");
82 : 1 : json_builder_begin_array (builder);
83 : :
84 : 1 : json_object_iter_init (&iter, valent_packet_get_body (packet));
85 [ + + ]: 5 : while (json_object_iter_next (&iter, &uid, &node))
86 : : {
87 : 4 : int64_t timestamp = 0;
88 : :
89 : : // skip the "uids" array
90 [ + + ]: 4 : if G_UNLIKELY (g_str_equal ("uids", uid))
91 : 1 : continue;
92 : :
93 [ + - ]: 3 : if G_LIKELY (json_node_get_value_type (node) == G_TYPE_INT64)
94 : 3 : timestamp = json_node_get_int (node);
95 : :
96 : : // TODO
97 [ - + ]: 3 : if (timestamp != 0)
98 : : {
99 : 3 : json_builder_add_string_value (builder, uid);
100 : 3 : n_requested++;
101 : : }
102 : : }
103 : :
104 : 1 : json_builder_end_array (builder);
105 : 1 : request = valent_packet_end (&builder);
106 : :
107 [ + - ]: 1 : if (n_requested > 0)
108 : : {
109 : 1 : valent_device_send_packet (self->device,
110 : : request,
111 : : self->cancellable,
112 : : (GAsyncReadyCallback) valent_contacts_device_send_packet_cb,
113 : : NULL);
114 : : }
115 : :
116 [ - + ]: 1 : VALENT_EXIT;
117 : : }
118 : :
119 : : static void
120 : 1 : valent_contacts_device_handle_response_vcards (ValentContactsDevice *self,
121 : : JsonNode *packet)
122 : : {
123 : 2 : g_autoptr (TrackerSparqlConnection) connection = NULL;
124 [ + - ]: 1 : g_autoptr (TrackerResource) list_resource = NULL;
125 : 1 : ValentDevice *device;
126 : 1 : const char *list_name;
127 : 1 : JsonObjectIter iter;
128 : 1 : const char *uid;
129 : 1 : JsonNode *node;
130 : :
131 : 1 : VALENT_ENTRY;
132 : :
133 [ + - ]: 1 : g_assert (VALENT_IS_CONTACTS_DEVICE (self));
134 [ - + ]: 1 : g_assert (VALENT_IS_PACKET (packet));
135 : :
136 : 1 : device = valent_resource_get_source (VALENT_RESOURCE (self));
137 : 1 : list_name = valent_device_get_name (device);
138 : :
139 : 1 : list_resource = tracker_resource_new (self->default_iri);
140 : 1 : tracker_resource_set_uri (list_resource, "rdf:type", "nco:ContactList");
141 : 1 : tracker_resource_set_string (list_resource, "nie:title", list_name);
142 : :
143 : 1 : json_object_iter_init (&iter, valent_packet_get_body (packet));
144 [ + + ]: 6 : while (json_object_iter_next (&iter, &uid, &node))
145 : : {
146 : 4 : TrackerResource *item_resource = NULL;
147 : 6 : g_autoptr (EContact) contact = NULL;
148 : 4 : const char *vcard;
149 : :
150 : : /* NOTE: This has the side-effect of ignoring `uids` array
151 : : */
152 [ + + ]: 4 : if G_UNLIKELY (json_node_get_value_type (node) != G_TYPE_STRING)
153 : 1 : continue;
154 : :
155 : 3 : vcard = json_node_get_string (node);
156 : 3 : contact = e_contact_new_from_vcard_with_uid (vcard, uid);
157 : 3 : item_resource = valent_contact_resource_from_econtact (contact);
158 [ + - ]: 3 : if (item_resource != NULL)
159 : : {
160 : 3 : g_autofree char *item_urn = NULL;
161 : :
162 : 3 : item_urn = tracker_sparql_escape_uri_printf ("%s:%s",
163 : : self->default_iri,
164 : : uid);
165 : 3 : tracker_resource_set_identifier (item_resource, item_urn);
166 : 3 : tracker_resource_add_take_relation (list_resource,
167 : : "nco:containsContact",
168 : 3 : g_steal_pointer (&item_resource));
169 : : }
170 : : }
171 : :
172 : 1 : g_object_get (self, "connection", &connection, NULL);
173 : 1 : tracker_sparql_connection_update_resource_async (connection,
174 : : VALENT_CONTACTS_GRAPH,
175 : : list_resource,
176 : : self->cancellable,
177 : : (GAsyncReadyCallback) execute_add_contacts_cb,
178 : : NULL);
179 : :
180 [ + - ]: 1 : VALENT_EXIT;
181 : : }
182 : :
183 : : static void
184 : 4 : valent_contacts_device_request_all_uids_timestamps (ValentContactsDevice *self)
185 : : {
186 : 8 : g_autoptr (JsonNode) packet = NULL;
187 : :
188 [ + - ]: 4 : g_assert (VALENT_IS_CONTACTS_DEVICE (self));
189 : :
190 : 4 : packet = valent_packet_new ("kdeconnect.contacts.request_all_uids_timestamps");
191 [ + - ]: 4 : valent_device_send_packet (self->device,
192 : : packet,
193 : : self->cancellable,
194 : : (GAsyncReadyCallback) valent_contacts_device_send_packet_cb,
195 : : NULL);
196 : 4 : }
197 : :
198 : : static void
199 : 13 : on_device_state_changed (ValentDevice *device,
200 : : GParamSpec *pspec,
201 : : ValentContactsDevice *self)
202 : : {
203 : 13 : ValentDeviceState state = VALENT_DEVICE_STATE_NONE;
204 : 13 : gboolean available;
205 : :
206 : 13 : state = valent_device_get_state (device);
207 : 13 : available = (state & VALENT_DEVICE_STATE_CONNECTED) != 0 &&
208 : : (state & VALENT_DEVICE_STATE_PAIRED) != 0;
209 : :
210 [ + + + - ]: 13 : if (available && self->cancellable == NULL)
211 : : {
212 : 17 : g_autoptr (GCancellable) cancellable = NULL;
213 : :
214 : 4 : cancellable = g_cancellable_new ();
215 : 4 : self->cancellable = valent_object_chain_cancellable (VALENT_OBJECT (self),
216 : : cancellable);
217 [ + - ]: 4 : valent_contacts_device_request_all_uids_timestamps (self);
218 : : }
219 [ + + ]: 9 : else if (!available && self->cancellable != NULL)
220 : : {
221 : 4 : g_cancellable_cancel (self->cancellable);
222 [ + - ]: 4 : g_clear_object (&self->cancellable);
223 : : }
224 : 13 : }
225 : :
226 : : /*
227 : : * GObject
228 : : */
229 : : static void
230 : 5 : valent_contacts_device_constructed (GObject *object)
231 : : {
232 : 5 : ValentContactsDevice *self = VALENT_CONTACTS_DEVICE (object);
233 : 5 : const char *iri = NULL;
234 : :
235 : 5 : G_OBJECT_CLASS (valent_contacts_device_parent_class)->constructed (object);
236 : :
237 : 5 : self->device = valent_resource_get_source (VALENT_RESOURCE (self));
238 : 5 : g_signal_connect_object (self->device,
239 : : "notify::state",
240 : : G_CALLBACK (on_device_state_changed),
241 : : self,
242 : : G_CONNECT_DEFAULT);
243 : :
244 : 5 : iri = valent_resource_get_iri (VALENT_RESOURCE (self));
245 : 5 : self->default_iri = tracker_sparql_escape_uri_printf ("%s:default", iri);
246 : 5 : }
247 : :
248 : : static void
249 : 5 : valent_contacts_device_finalize (GObject *object)
250 : : {
251 : 5 : ValentContactsDevice *self = VALENT_CONTACTS_DEVICE (object);
252 : :
253 [ + - ]: 5 : g_clear_pointer (&self->default_iri, g_free);
254 [ - + ]: 5 : g_clear_object (&self->get_timestamp_stmt);
255 [ - + ]: 5 : g_clear_object (&self->cancellable);
256 : :
257 : 5 : G_OBJECT_CLASS (valent_contacts_device_parent_class)->finalize (object);
258 : 5 : }
259 : :
260 : : static void
261 : 2 : valent_contacts_device_class_init (ValentContactsDeviceClass *klass)
262 : : {
263 : 2 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
264 : :
265 : 2 : object_class->constructed = valent_contacts_device_constructed;
266 : 2 : object_class->finalize = valent_contacts_device_finalize;
267 : : }
268 : :
269 : : static void
270 : 5 : valent_contacts_device_init (ValentContactsDevice *self)
271 : : {
272 : 5 : }
273 : :
274 : : /*< private >
275 : : * valent_contacts_device_new:
276 : : * @device: a `ValentDevice`
277 : : *
278 : : * Create a new `ValentContactsDevice`.
279 : : *
280 : : * Returns: (transfer full): a new adapter
281 : : */
282 : : ValentContactsAdapter *
283 : 5 : valent_contacts_device_new (ValentDevice *device)
284 : : {
285 : 10 : g_autoptr (ValentContext) context = NULL;
286 [ + - ]: 5 : g_autofree char *iri = NULL;
287 : :
288 [ + - ]: 5 : g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
289 : :
290 : 5 : context = valent_context_new (valent_device_get_context (device),
291 : : "plugin",
292 : : "contacts");
293 : 5 : iri = tracker_sparql_escape_uri_printf ("urn:valent:contacts:%s",
294 : : valent_device_get_id (device));
295 : 5 : return g_object_new (VALENT_TYPE_CONTACTS_DEVICE,
296 : : "iri", iri,
297 : : "context", context,
298 : : "source", device,
299 : : "title", valent_device_get_name (device),
300 : : NULL);
301 : : }
302 : :
303 : : /*< private >
304 : : * valent_contacts_device_handle_packet:
305 : : * @adapter: a `ValentContactsAdapter`
306 : : * @type: a KDE Connect packet type
307 : : * @packet: a KDE Connect packet
308 : : *
309 : : * Handle an incoming `kdeconnect.contacts.*` packet.
310 : : */
311 : : void
312 : 2 : valent_contacts_device_handle_packet (ValentContactsAdapter *adapter,
313 : : const char *type,
314 : : JsonNode *packet)
315 : : {
316 : 2 : ValentContactsDevice *self = VALENT_CONTACTS_DEVICE (adapter);
317 : :
318 [ + - ]: 2 : g_assert (VALENT_IS_CONTACTS_DEVICE (adapter));
319 [ - + ]: 2 : g_assert (type != NULL);
320 [ - + ]: 2 : g_assert (VALENT_IS_PACKET (packet));
321 : :
322 : : /* A response to a request for a listing of contacts
323 : : */
324 [ + + ]: 2 : if (g_str_equal (type, "kdeconnect.contacts.response_uids_timestamps"))
325 : 1 : valent_contacts_device_handle_response_uids_timestamps (self, packet);
326 : :
327 : : /* A response to a request for vCards
328 : : */
329 [ + - ]: 1 : else if (g_str_equal (type, "kdeconnect.contacts.response_vcards"))
330 : 1 : valent_contacts_device_handle_response_vcards (self, packet);
331 : :
332 : : else
333 : 0 : g_assert_not_reached ();
334 : 2 : }
335 : :
|