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