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 [ + + + - ]: 14 : 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 [ # # ]: 0 : if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED))
57 : 0 : g_critical ("%s(): %s", G_STRFUNC, error->message);
58 [ # # ]: 0 : 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 [ # # ]: 0 : else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
61 : 0 : 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 : 8 : on_device_state_changed (ValentDevice *device,
200 : : GParamSpec *pspec,
201 : : ValentContactsDevice *self)
202 : : {
203 : 8 : ValentDeviceState state = VALENT_DEVICE_STATE_NONE;
204 : 8 : gboolean available;
205 : :
206 : 8 : state = valent_device_get_state (device);
207 : 8 : available = (state & VALENT_DEVICE_STATE_CONNECTED) != 0 &&
208 : : (state & VALENT_DEVICE_STATE_PAIRED) != 0;
209 : :
210 [ + + + - ]: 8 : if (available && self->cancellable == NULL)
211 : : {
212 : 4 : self->cancellable = g_cancellable_new ();
213 : 4 : valent_contacts_device_request_all_uids_timestamps (self);
214 : : }
215 [ + - ]: 4 : else if (!available && self->cancellable != NULL)
216 : : {
217 : 4 : g_cancellable_cancel (self->cancellable);
218 [ + - ]: 4 : g_clear_object (&self->cancellable);
219 : : }
220 : 8 : }
221 : :
222 : : /*
223 : : * GObject
224 : : */
225 : : static void
226 : 4 : valent_contacts_device_constructed (GObject *object)
227 : : {
228 : 4 : ValentContactsDevice *self = VALENT_CONTACTS_DEVICE (object);
229 : 4 : const char *iri = NULL;
230 : :
231 : 4 : G_OBJECT_CLASS (valent_contacts_device_parent_class)->constructed (object);
232 : :
233 : 4 : self->device = valent_resource_get_source (VALENT_RESOURCE (self));
234 : 4 : g_signal_connect_object (self->device,
235 : : "notify::state",
236 : : G_CALLBACK (on_device_state_changed),
237 : : self,
238 : : G_CONNECT_DEFAULT);
239 : :
240 : 4 : iri = valent_resource_get_iri (VALENT_RESOURCE (self));
241 : 4 : self->default_iri = tracker_sparql_escape_uri_printf ("%s:default", iri);
242 : 4 : }
243 : :
244 : : static void
245 : 4 : valent_contacts_device_finalize (GObject *object)
246 : : {
247 : 4 : ValentContactsDevice *self = VALENT_CONTACTS_DEVICE (object);
248 : :
249 [ + - ]: 4 : g_clear_pointer (&self->default_iri, g_free);
250 [ - + ]: 4 : g_clear_object (&self->get_timestamp_stmt);
251 [ - + ]: 4 : g_clear_object (&self->cancellable);
252 : :
253 : 4 : G_OBJECT_CLASS (valent_contacts_device_parent_class)->finalize (object);
254 : 4 : }
255 : :
256 : : static void
257 : 1 : valent_contacts_device_class_init (ValentContactsDeviceClass *klass)
258 : : {
259 : 1 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
260 : :
261 : 1 : object_class->constructed = valent_contacts_device_constructed;
262 : 1 : object_class->finalize = valent_contacts_device_finalize;
263 : : }
264 : :
265 : : static void
266 : 4 : valent_contacts_device_init (ValentContactsDevice *self)
267 : : {
268 : 4 : }
269 : :
270 : : /*< private >
271 : : * valent_contacts_device_new:
272 : : * @device: a `ValentDevice`
273 : : *
274 : : * Create a new `ValentContactsDevice`.
275 : : *
276 : : * Returns: (transfer full): a new adapter
277 : : */
278 : : ValentContactsAdapter *
279 : 4 : valent_contacts_device_new (ValentDevice *device)
280 : : {
281 : 8 : g_autoptr (ValentContext) context = NULL;
282 [ + - ]: 4 : g_autofree char *iri = NULL;
283 : :
284 [ + - ]: 4 : g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
285 : :
286 : 4 : context = valent_context_new (valent_device_get_context (device),
287 : : "plugin",
288 : : "contacts");
289 : 4 : iri = tracker_sparql_escape_uri_printf ("urn:valent:contacts:%s",
290 : : valent_device_get_id (device));
291 : 4 : return g_object_new (VALENT_TYPE_CONTACTS_DEVICE,
292 : : "iri", iri,
293 : : "context", context,
294 : : "source", device,
295 : : "title", valent_device_get_name (device),
296 : : NULL);
297 : : }
298 : :
299 : : /*< private >
300 : : * valent_contacts_device_handle_packet:
301 : : * @adapter: a `ValentContactsAdapter`
302 : : * @type: a KDE Connect packet type
303 : : * @packet: a KDE Connect packet
304 : : *
305 : : * Handle an incoming `kdeconnect.contacts.*` packet.
306 : : */
307 : : void
308 : 2 : valent_contacts_device_handle_packet (ValentContactsAdapter *adapter,
309 : : const char *type,
310 : : JsonNode *packet)
311 : : {
312 : 2 : ValentContactsDevice *self = VALENT_CONTACTS_DEVICE (adapter);
313 : :
314 [ + - ]: 2 : g_assert (VALENT_IS_CONTACTS_DEVICE (adapter));
315 [ - + ]: 2 : g_assert (type != NULL);
316 [ - + ]: 2 : g_assert (VALENT_IS_PACKET (packet));
317 : :
318 : : /* A response to a request for a listing of contacts
319 : : */
320 [ + + ]: 2 : if (g_str_equal (type, "kdeconnect.contacts.response_uids_timestamps"))
321 : 1 : valent_contacts_device_handle_response_uids_timestamps (self, packet);
322 : :
323 : : /* A response to a request for vCards
324 : : */
325 [ + - ]: 1 : else if (g_str_equal (type, "kdeconnect.contacts.response_vcards"))
326 : 1 : valent_contacts_device_handle_response_vcards (self, packet);
327 : :
328 : : else
329 : 0 : g_assert_not_reached ();
330 : 2 : }
331 : :
|