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-sms-device"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <inttypes.h>
9 : :
10 : : #include <gio/gio.h>
11 : : #include <libtracker-sparql/tracker-sparql.h>
12 : : #include <valent.h>
13 : :
14 : : #include "valent-sms-device.h"
15 : :
16 : : #define GET_TIMESTAMP_RQ "/ca/andyholmes/Valent/sparql/get-timestamp.rq"
17 : :
18 : : struct _ValentSmsDevice
19 : : {
20 : : ValentMessagesAdapter parent_instance;
21 : :
22 : : ValentDevice *device;
23 : : TrackerSparqlConnection *connection;
24 : : TrackerSparqlStatement *get_timestamp_stmt;
25 : :
26 : : GCancellable *cancellable;
27 : : GPtrArray *message_requests;
28 : : GQueue attachment_requests;
29 : : };
30 : :
31 : : static void attachment_request_next (ValentSmsDevice *self);
32 : : static void attachment_request_queue (ValentSmsDevice *self,
33 : : const char *iri,
34 : : int64_t part_id,
35 : : const char *unique_identifier);
36 : : static void valent_sms_device_request_attachment (ValentSmsDevice *self,
37 : : int64_t thread_id,
38 : : const char *unique_identifier);
39 : : static void message_request_free (gpointer data);
40 : : static void valent_sms_device_request_conversation (ValentSmsDevice *self,
41 : : int64_t thread_id,
42 : : int64_t range_start_timestamp,
43 : : int64_t number_to_request);
44 : :
45 [ + + + - ]: 27 : G_DEFINE_FINAL_TYPE (ValentSmsDevice, valent_sms_device, VALENT_TYPE_MESSAGES_ADAPTER)
46 : :
47 : :
48 : : typedef struct
49 : : {
50 : : char *iri;
51 : : int64_t part_id;
52 : : char *unique_identifier;
53 : : } AttachmentRequest;
54 : :
55 : : static void
56 : 1 : attachment_request_free (gpointer data)
57 : : {
58 : 1 : AttachmentRequest *request = data;
59 : :
60 [ + - ]: 1 : g_clear_pointer (&request->iri, g_free);
61 [ + - ]: 1 : g_clear_pointer (&request->unique_identifier, g_free);
62 : 1 : g_free (request);
63 : 1 : }
64 : :
65 : : static void
66 : 1 : attachment_request_next_cb (GFile *file,
67 : : GAsyncResult *result,
68 : : ValentSmsDevice *self)
69 : : {
70 : 1 : g_autoptr (GFileInfo) info = NULL;
71 [ - - ]: 1 : g_autoptr (GError) error = NULL;
72 : :
73 : 1 : info = g_file_query_info_finish (file, result, &error);
74 [ + - ]: 1 : if (info == NULL)
75 : : {
76 [ + - ]: 1 : if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
77 : : return;
78 : :
79 [ + - ]: 1 : if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
80 : : {
81 : 1 : AttachmentRequest *request;
82 : :
83 : 1 : request = g_queue_peek_head (&self->attachment_requests);
84 : 1 : valent_sms_device_request_attachment (self,
85 : : request->part_id,
86 : 1 : request->unique_identifier);
87 : 1 : return;
88 : : }
89 : :
90 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
91 : : }
92 : :
93 : 0 : attachment_request_free (g_queue_pop_head (&self->attachment_requests));
94 [ # # ]: 0 : attachment_request_next (self);
95 : : }
96 : :
97 : : static void
98 : 2 : attachment_request_next (ValentSmsDevice *self)
99 : : {
100 [ + - ]: 2 : g_assert (VALENT_IS_SMS_DEVICE (self));
101 : :
102 [ + + ]: 2 : if (!g_queue_is_empty (&self->attachment_requests))
103 : : {
104 : 1 : AttachmentRequest *request;
105 : 1 : ValentContext *context;
106 : 3 : g_autoptr (GFile) file = NULL;
107 : :
108 : 1 : request = g_queue_peek_head (&self->attachment_requests);
109 : 1 : context = valent_extension_get_context (VALENT_EXTENSION (self));
110 : 1 : file = valent_context_get_cache_file (context, request->unique_identifier);
111 [ + - ]: 1 : g_file_query_info_async (file,
112 : : G_FILE_ATTRIBUTE_STANDARD_TYPE,
113 : : G_FILE_QUERY_INFO_NONE,
114 : : G_PRIORITY_DEFAULT,
115 : : self->cancellable,
116 : : (GAsyncReadyCallback) attachment_request_next_cb,
117 : : self);
118 : : }
119 : 2 : }
120 : :
121 : : static void
122 : 1 : attachment_request_queue (ValentSmsDevice *self,
123 : : const char *iri,
124 : : int64_t part_id,
125 : : const char *unique_identifier)
126 : : {
127 : 1 : AttachmentRequest *request;
128 : 1 : gboolean start = g_queue_is_empty (&self->attachment_requests);
129 : :
130 : 1 : request = g_new0 (AttachmentRequest, 1);
131 [ - + ]: 1 : request->iri = g_strdup (iri);
132 : 1 : request->part_id = part_id;
133 [ - + ]: 1 : request->unique_identifier = g_strdup (unique_identifier);
134 : 1 : g_queue_push_tail (&self->attachment_requests, g_steal_pointer (&request));
135 : :
136 [ + - ]: 1 : if (start)
137 : 1 : attachment_request_next (self);
138 : 1 : }
139 : :
140 : : static void
141 : 0 : update_attachment_cb (TrackerSparqlConnection *connection,
142 : : GAsyncResult *result,
143 : : gpointer user_data)
144 : : {
145 : 0 : g_autoptr (GError) error = NULL;
146 : :
147 [ # # ]: 0 : if (!tracker_sparql_connection_update_resource_finish (connection, result, &error))
148 : : {
149 [ # # ]: 0 : if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
150 [ # # ]: 0 : return;
151 : :
152 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
153 : : }
154 : : }
155 : :
156 : : static void
157 : 1 : attachment_request_query_cb (GFile *file,
158 : : GAsyncResult *result,
159 : : ValentSmsDevice *self)
160 : : {
161 : 1 : const char *iri = NULL;
162 : 1 : g_autofree char *uri = NULL;
163 : 1 : g_autoptr (TrackerResource) attachment = NULL;
164 [ # # ]: 0 : g_autoptr (GDateTime) accessed = NULL;
165 [ # # ]: 0 : g_autoptr (GDateTime) created = NULL;
166 [ - - ]: 1 : g_autoptr (GDateTime) modified = NULL;
167 [ - - ]: 1 : g_autoptr (GFileInfo) info = NULL;
168 : 1 : g_autoptr (GError) error = NULL;
169 : :
170 : 1 : info = g_file_query_info_finish (file, result, &error);
171 [ + - ]: 1 : if (info == NULL)
172 : : {
173 [ - + ]: 1 : if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
174 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
175 : :
176 [ + - ]: 1 : return;
177 : : }
178 : :
179 : 0 : iri = g_object_get_data (G_OBJECT (file), "valent-message-attachment-iri");
180 : 0 : uri = g_file_get_uri (file);
181 : :
182 : 0 : attachment = tracker_resource_new (iri);
183 : 0 : tracker_resource_set_uri (attachment, "rdf:type", "nfo:Attachment");
184 : 0 : tracker_resource_set_string (attachment, "nie:url", uri);
185 : 0 : tracker_resource_set_string (attachment,
186 : : "nfo:fileName", g_file_info_get_name (info));
187 : 0 : tracker_resource_set_int64 (attachment,
188 : : "nfo:fileSize", g_file_info_get_size (info));
189 : :
190 : 0 : created = g_file_info_get_creation_date_time (info);
191 [ # # ]: 0 : if (created != NULL)
192 : 0 : tracker_resource_set_datetime (attachment, "nfo:fileCreated", created);
193 : :
194 : 0 : accessed = g_file_info_get_access_date_time (info);
195 [ # # ]: 0 : if (accessed != NULL)
196 : 0 : tracker_resource_set_datetime (attachment, "nfo:fileLastAccessed", accessed);
197 : :
198 : 0 : modified = g_file_info_get_modification_date_time (info);
199 [ # # ]: 0 : if (modified != NULL)
200 : 0 : tracker_resource_set_datetime (attachment, "nfo:fileLastModified", modified);
201 : :
202 [ # # ]: 0 : tracker_sparql_connection_update_resource_async (self->connection,
203 : : VALENT_MESSAGES_GRAPH,
204 : : attachment,
205 : : self->cancellable,
206 : : (GAsyncReadyCallback)update_attachment_cb,
207 : : NULL);
208 : : }
209 : :
210 : : static void
211 : 1 : handle_attachment_file_cb (ValentTransfer *transfer,
212 : : GAsyncResult *result,
213 : : ValentSmsDevice *self)
214 : : {
215 : 1 : g_autoptr (GError) error = NULL;
216 : :
217 [ - + ]: 1 : if (!valent_transfer_execute_finish (transfer, result, &error))
218 : : {
219 [ # # ]: 0 : if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
220 [ # # ]: 0 : return;
221 : :
222 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
223 : : }
224 : : else
225 : : {
226 : 1 : AttachmentRequest *request = g_queue_peek_head (&self->attachment_requests);
227 : 2 : g_autoptr (GFile) file = NULL;
228 : :
229 : 1 : file = valent_device_transfer_ref_file (VALENT_DEVICE_TRANSFER (transfer));
230 : 2 : g_object_set_data_full (G_OBJECT (file),
231 : : "valent-message-attachment-iri",
232 [ - + ]: 2 : g_strdup (request->iri),
233 : : g_free);
234 [ + - ]: 1 : g_file_query_info_async (file,
235 : : "standard::*",
236 : : G_FILE_QUERY_INFO_NONE,
237 : : G_PRIORITY_DEFAULT,
238 : : self->cancellable,
239 : : (GAsyncReadyCallback)attachment_request_query_cb,
240 : : self);
241 : : }
242 : :
243 [ + - ]: 1 : if (!g_queue_is_empty (&self->attachment_requests))
244 : : {
245 : 1 : attachment_request_free (g_queue_pop_head (&self->attachment_requests));
246 : 1 : attachment_request_next (self);
247 : : }
248 : : }
249 : :
250 : :
251 : : static inline TrackerResource *
252 : 9 : valent_message_resource_from_json (ValentSmsDevice *self,
253 : : TrackerResource *thread,
254 : : JsonNode *root)
255 : : {
256 : 9 : TrackerResource *message;
257 : 9 : TrackerResource *box;
258 : 9 : JsonNode *node;
259 : 9 : JsonObject *object;
260 : 18 : g_autoptr (GDateTime) datetime = NULL;
261 [ + - ]: 9 : g_autofree char *iri = NULL;
262 : 9 : int64_t date;
263 : 9 : int64_t message_id;
264 : 9 : int64_t message_type;
265 : 9 : gboolean read;
266 : 9 : const char *sender = NULL;
267 : 9 : int64_t sub_id;
268 : 9 : const char *text = NULL;
269 : 9 : int64_t thread_id;
270 : :
271 [ + - ]: 9 : g_return_val_if_fail (JSON_NODE_HOLDS_OBJECT (root), NULL);
272 : :
273 : : /* Check all the required fields exist
274 : : */
275 : 9 : object = json_node_get_object (root);
276 : 9 : node = json_object_get_member (object, "thread_id");
277 [ + - - + ]: 9 : if (node == NULL || json_node_get_value_type (node) != G_TYPE_INT64)
278 : : {
279 : 0 : g_warning ("%s(): expected \"thread_id\" field holding an integer",
280 : : G_STRFUNC);
281 : 0 : return NULL;
282 : : }
283 : 9 : thread_id = json_node_get_int (node);
284 [ - + ]: 9 : g_return_val_if_fail (thread_id >= 0, NULL);
285 : :
286 : 9 : node = json_object_get_member (object, "_id");
287 [ + - - + ]: 9 : if (node == NULL || json_node_get_value_type (node) != G_TYPE_INT64)
288 : : {
289 : 0 : g_warning ("%s(): expected \"_id\" field holding an integer", G_STRFUNC);
290 : 0 : return NULL;
291 : : }
292 : 9 : message_id = json_node_get_int (node);
293 : :
294 : 9 : node = json_object_get_member (object, "date");
295 [ + - - + ]: 9 : if (node == NULL || json_node_get_value_type (node) != G_TYPE_INT64)
296 : : {
297 : 0 : g_warning ("%s(): expected \"date\" field holding an integer", G_STRFUNC);
298 : 0 : return NULL;
299 : : }
300 : 9 : date = json_node_get_int (node);
301 : :
302 : 9 : node = json_object_get_member (object, "type");
303 [ + - - + ]: 9 : if (node == NULL || json_node_get_value_type (node) != G_TYPE_INT64)
304 : : {
305 : 0 : g_warning ("%s(): expected \"type\" field holding an integer", G_STRFUNC);
306 : 0 : return NULL;
307 : : }
308 : 9 : message_type = json_node_get_int (node);
309 : :
310 : : /* PhoneMessage
311 : : */
312 : 9 : iri = g_strdup_printf ("%s:%"PRId64,
313 : : tracker_resource_get_identifier (thread),
314 : : message_id);
315 : 9 : message = tracker_resource_new (iri);
316 : 9 : tracker_resource_set_uri (message, "rdf:type", "vmo:PhoneMessage");
317 : 9 : tracker_resource_set_int64 (message, "vmo:phoneMessageId", message_id);
318 : :
319 : 9 : datetime = g_date_time_new_from_unix_local_usec (date * 1000);
320 [ + + ]: 9 : if (message_type == VALENT_MESSAGE_BOX_SENT)
321 : 7 : tracker_resource_set_datetime (message, "nmo:sentDate", datetime);
322 [ + - ]: 2 : else if (message_type == VALENT_MESSAGE_BOX_INBOX)
323 : 2 : tracker_resource_set_datetime (message, "nmo:receivedDate", datetime);
324 : :
325 : 9 : read = !!json_object_get_int_member_with_default (object, "read", 0);
326 : 9 : tracker_resource_set_boolean (message, "nmo:isRead", read);
327 : :
328 : 9 : text = json_object_get_string_member_with_default (object, "body", NULL);
329 [ + - + - ]: 9 : if (text != NULL && *text != '\0')
330 : 9 : tracker_resource_set_string (message, "nmo:plainTextMessageContent", text);
331 : :
332 [ - + + - : 9 : switch (message_type)
- - - - ]
333 : : {
334 : 0 : case VALENT_MESSAGE_BOX_ALL:
335 : 0 : box = tracker_resource_new ("vmo:android-message-type-all");
336 : 0 : break;
337 : :
338 : 2 : case VALENT_MESSAGE_BOX_INBOX:
339 : 2 : box = tracker_resource_new ("vmo:android-message-type-inbox");
340 : 2 : break;
341 : :
342 : 7 : case VALENT_MESSAGE_BOX_SENT:
343 : 7 : box = tracker_resource_new ("vmo:android-message-type-sent");
344 : 7 : break;
345 : :
346 : 0 : case VALENT_MESSAGE_BOX_DRAFTS:
347 : 0 : box = tracker_resource_new ("vmo:android-message-type-drafts");
348 : 0 : break;
349 : :
350 : 0 : case VALENT_MESSAGE_BOX_OUTBOX:
351 : 0 : box = tracker_resource_new ("vmo:android-message-type-outbox");
352 : 0 : break;
353 : :
354 : 0 : case VALENT_MESSAGE_BOX_FAILED:
355 : 0 : box = tracker_resource_new ("vmo:android-message-type-failed");
356 : 0 : break;
357 : :
358 : 0 : case VALENT_MESSAGE_BOX_QUEUED:
359 : 0 : box = tracker_resource_new ("vmo:android-message-type-queued");
360 : 0 : break;
361 : :
362 : 0 : default:
363 : 0 : box = tracker_resource_new ("vmo:android-message-type-all");
364 : 0 : g_warn_if_reached ();
365 : 0 : break;
366 : : }
367 : 9 : tracker_resource_add_take_relation (message,
368 : : "vmo:phoneMessageBox",
369 : 9 : g_steal_pointer (&box));
370 : :
371 : 9 : sub_id = json_object_get_int_member_with_default (object, "sub_id", -1);
372 : 9 : tracker_resource_set_int64 (message, "vmo:subscriptionId", sub_id);
373 : :
374 : : /* This is an inferred data point from kdeconnect-android, with the bits 0x1
375 : : * set if the content type is `text/plain` and 0x2 if the message has more than
376 : : * two participants (0x0 if neither were true).
377 : : */
378 : : #if 0
379 : : int64_t event = json_object_get_int_member_with_default (object, "event", 0);
380 : : #endif
381 : :
382 : 9 : node = json_object_get_member (object, "addresses");
383 [ + - + - ]: 9 : if (node != NULL && JSON_NODE_HOLDS_ARRAY (node))
384 : : {
385 : 9 : JsonArray *addresses = json_node_get_array (node);
386 : 9 : unsigned int n_addresses = json_array_get_length (addresses);
387 : :
388 [ + + ]: 19 : for (unsigned int i = 0; i < n_addresses; i++)
389 : : {
390 : 10 : JsonObject *address = json_array_get_object_element (addresses, i);
391 : 10 : g_autoptr (TrackerResource) medium = NULL;
392 : 10 : g_autofree char *medium_iri = NULL;
393 : 10 : const char *address_str;
394 : :
395 : 10 : address_str = json_object_get_string_member (address, "address");
396 [ + - - + ]: 10 : if (address_str == NULL || *address_str == '\0')
397 : 0 : continue;
398 : :
399 : : /* Sometimes the sender's address is duplicated in the remainder of
400 : : * the list, which is reserved for recipients.
401 : : */
402 [ - + ]: 10 : if (g_strcmp0 (sender, address_str) == 0)
403 : : {
404 : 0 : VALENT_NOTE ("skipping duplicate contact medium \"%s\"", sender);
405 : 0 : continue;
406 : : }
407 : :
408 : : /* Messages may be sent to or from email addresses.
409 : : */
410 [ - + ]: 10 : if (g_strrstr (address_str, "@"))
411 : : {
412 : 0 : medium_iri = g_strdup_printf ("mailto:%s", address_str);
413 : 0 : medium = tracker_resource_new (medium_iri);
414 : 0 : tracker_resource_set_uri (medium, "rdf:type", "nco:EmailAddress");
415 : 0 : tracker_resource_set_string (medium, "nco:emailAddress", address_str);
416 : : }
417 : : else
418 : : {
419 : 10 : g_autoptr (EPhoneNumber) number = NULL;
420 : :
421 : 10 : number = e_phone_number_from_string (address_str, NULL, NULL);
422 [ - + ]: 10 : if (number == NULL)
423 : : {
424 : 0 : VALENT_NOTE ("invalid phone number \"%s\"", address_str);
425 : 0 : continue;
426 : : }
427 : :
428 : 10 : medium_iri = e_phone_number_to_string (number, E_PHONE_NUMBER_FORMAT_RFC3966);
429 : 10 : medium = tracker_resource_new (medium_iri);
430 : 10 : tracker_resource_set_uri (medium, "rdf:type", "nco:PhoneNumber");
431 : 10 : tracker_resource_set_string (medium, "nco:phoneNumber", address_str);
432 : : }
433 : :
434 : : /* If the message is incoming, the first address is the sender. Mark
435 : : * the sender in case it is duplicated in the recipients.
436 : : */
437 [ + + ]: 10 : if (i == 0 && message_type == VALENT_MESSAGE_BOX_INBOX)
438 : : {
439 : 2 : sender = address_str;
440 : 2 : tracker_resource_add_relation (message,
441 : : "nmo:messageFrom",
442 : : medium);
443 : 2 : tracker_resource_add_relation (message,
444 : : "nmo:messageSender",
445 : : medium);
446 : : }
447 : : else
448 : : {
449 : 8 : tracker_resource_add_relation (message,
450 : : "nmo:primaryMessageRecipient",
451 : : medium);
452 : : }
453 : :
454 : : // TODO: does this result in an exclusive set?
455 : 10 : tracker_resource_add_take_relation (thread,
456 : : "vmo:hasParticipant",
457 : 10 : g_steal_pointer (&medium));
458 : : }
459 : : }
460 : 9 : tracker_resource_add_relation (message, "vmo:communicationChannel", thread);
461 : :
462 : 9 : node = json_object_get_member (object, "attachments");
463 [ + + - + ]: 9 : if (node != NULL && JSON_NODE_HOLDS_ARRAY (node))
464 : : {
465 : 1 : JsonArray *attachments = json_node_get_array (node);
466 : 1 : unsigned int n_attachments = json_array_get_length (attachments);
467 : :
468 [ + + ]: 2 : for (unsigned int i = 0; i < n_attachments; i++)
469 : : {
470 : 1 : JsonObject *attachment = json_array_get_object_element (attachments, i);
471 : 1 : JsonNode *subnode;
472 : 1 : TrackerResource *rel;
473 : 1 : g_autofree char *rel_iri = NULL;
474 : 1 : int64_t part_id = 0;
475 : 1 : const char *unique_identifier = NULL;
476 : :
477 : : /* NOTE: `part_id` and `mime_type` are not stored in the graph.
478 : : */
479 : 1 : subnode = json_object_get_member (attachment, "part_id");
480 [ + - - + ]: 1 : if (subnode == NULL || json_node_get_value_type (subnode) != G_TYPE_INT64)
481 : 0 : continue;
482 : :
483 : 1 : part_id = json_node_get_int (subnode);
484 : :
485 : 1 : subnode = json_object_get_member (attachment, "unique_identifier");
486 [ + - - + ]: 1 : if (subnode == NULL || json_node_get_value_type (subnode) != G_TYPE_STRING)
487 : 0 : continue;
488 : :
489 : 1 : unique_identifier = json_node_get_string (subnode);
490 : :
491 : 1 : rel_iri = g_strdup_printf ("%s:%s", iri, unique_identifier);
492 : 1 : rel = tracker_resource_new (rel_iri);
493 : 1 : tracker_resource_set_uri (rel, "rdf:type", "nfo:Attachment");
494 : 1 : tracker_resource_set_string (rel, "nfo:fileName", unique_identifier);
495 : :
496 : 1 : subnode = json_object_get_member (attachment, "encoded_thumbnail");
497 [ + - + - ]: 1 : if (subnode != NULL && json_node_get_value_type (subnode) == G_TYPE_STRING)
498 : : {
499 : 1 : const char *encoded_thumbnail = NULL;
500 : :
501 : 1 : encoded_thumbnail = json_node_get_string (subnode);
502 : 1 : tracker_resource_set_string (rel, "vmo:encoded_thumbnail", encoded_thumbnail);
503 : : }
504 : :
505 : 1 : tracker_resource_add_take_relation (message,
506 : : "nmo:hasAttachment",
507 : 1 : g_steal_pointer (&rel));
508 : 1 : attachment_request_queue (self, rel_iri, part_id, unique_identifier);
509 : : }
510 : : }
511 : :
512 : : return message;
513 : : }
514 : :
515 : : static void
516 : 6 : execute_add_messages_cb (TrackerBatch *batch,
517 : : GAsyncResult *result,
518 : : gpointer user_data)
519 : : {
520 : 12 : g_autoptr (GError) error = NULL;
521 : :
522 [ - + - - ]: 6 : if (!tracker_batch_execute_finish (batch, result, &error) &&
523 : 0 : !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
524 : : {
525 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
526 : : }
527 : 6 : }
528 : :
529 : : static void
530 : 6 : valent_sms_device_add_json (ValentSmsDevice *self,
531 : : JsonNode *messages)
532 : : {
533 : 12 : g_autoptr (TrackerBatch) batch = NULL;
534 [ + - - - ]: 6 : g_autoptr (TrackerResource) thread = NULL;
535 : 6 : const char *base_urn = NULL;
536 [ + - - - ]: 6 : g_autofree char *thread_urn = NULL;
537 : 6 : int64_t thread_id;
538 : 6 : JsonArray *messages_;
539 : 6 : JsonObject *first_message;
540 : 6 : JsonNode *node;
541 : 6 : unsigned int n_messages;
542 : :
543 [ + - ]: 6 : g_assert (VALENT_IS_SMS_DEVICE (self));
544 [ - + ]: 6 : g_assert (JSON_NODE_HOLDS_ARRAY (messages));
545 : :
546 : 6 : batch = tracker_sparql_connection_create_batch (self->connection);
547 : :
548 : 6 : messages_ = json_node_get_array (messages);
549 : 6 : n_messages = json_array_get_length (messages_);
550 [ - + ]: 6 : g_return_if_fail (n_messages > 0);
551 : :
552 : 6 : first_message = json_node_get_object (json_array_get_element (messages_, 0));
553 : 6 : node = json_object_get_member (first_message, "thread_id");
554 [ + - - + ]: 6 : if (node == NULL || json_node_get_value_type (node) != G_TYPE_INT64)
555 : : {
556 : 0 : g_warning ("%s(): expected \"thread_id\" field holding an integer",
557 : : G_STRFUNC);
558 : 0 : return;
559 : : }
560 : :
561 : 6 : thread_id = json_node_get_int (node);
562 : 6 : base_urn = valent_resource_get_iri (VALENT_RESOURCE (self));
563 : 6 : thread_urn = g_strdup_printf ("%s:%"PRId64, base_urn, thread_id);
564 : 6 : thread = tracker_resource_new (thread_urn);
565 : 6 : tracker_resource_set_uri (thread, "rdf:type", "vmo:CommunicationChannel");
566 : 6 : tracker_resource_set_int64 (thread, "vmo:communicationChannelId", thread_id);
567 : :
568 [ + + ]: 15 : for (unsigned int i = 0; i < n_messages; i++)
569 : : {
570 : 9 : JsonNode *message = json_array_get_element (messages_, i);
571 : 9 : g_autoptr (TrackerResource) resource = NULL;
572 : :
573 : 9 : resource = valent_message_resource_from_json (self, thread, message);
574 [ + - ]: 9 : if (resource != NULL)
575 : 9 : tracker_batch_add_resource (batch, VALENT_MESSAGES_GRAPH, resource);
576 : : }
577 : :
578 : 6 : tracker_batch_execute_async (batch,
579 : : self->cancellable,
580 : : (GAsyncReadyCallback) execute_add_messages_cb,
581 : : NULL);
582 : : }
583 : :
584 : : static void
585 : 7 : valent_device_send_packet_cb (ValentDevice *device,
586 : : GAsyncResult *result,
587 : : gpointer user_data)
588 : : {
589 : 14 : g_autoptr (GError) error = NULL;
590 : :
591 [ - + ]: 7 : if (!valent_device_send_packet_finish (device, result, &error))
592 : : {
593 [ # # ]: 0 : if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED))
594 : 0 : g_critical ("%s(): %s", G_STRFUNC, error->message);
595 [ # # ]: 0 : else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_CONNECTED))
596 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
597 [ # # ]: 0 : else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
598 : 0 : g_debug ("%s(): %s", G_STRFUNC, error->message);
599 : : }
600 : 7 : }
601 : :
602 : : /*< private >
603 : : * @self: a `ValentSmsDevice`
604 : : * @part_id: the MMS part ID
605 : : * @unique_identifier: the attachment identifier
606 : : *
607 : : * Send a request for messages starting at @range_start_timestamp in
608 : : * oldest-to-newest order, for a maximum of @number_to_request.
609 : : */
610 : : static void
611 : 1 : valent_sms_device_request_attachment (ValentSmsDevice *self,
612 : : int64_t part_id,
613 : : const char *unique_identifier)
614 : : {
615 : 1 : g_autoptr (JsonBuilder) builder = NULL;
616 [ - + - - ]: 1 : g_autoptr (JsonNode) packet = NULL;
617 : :
618 [ + - ]: 1 : g_return_if_fail (VALENT_IS_SMS_DEVICE (self));
619 [ - + ]: 1 : g_return_if_fail (part_id >= 0);
620 [ + - - + ]: 1 : g_return_if_fail (unique_identifier != NULL && *unique_identifier != '\0');
621 : :
622 : 1 : valent_packet_init (&builder, "kdeconnect.sms.request_attachment");
623 : 1 : json_builder_set_member_name (builder, "part_id");
624 : 1 : json_builder_add_int_value (builder, part_id);
625 : 1 : json_builder_set_member_name (builder, "unique_identifier");
626 : 1 : json_builder_add_string_value (builder, unique_identifier);
627 : 1 : packet = valent_packet_end (&builder);
628 : :
629 [ + - ]: 1 : valent_device_send_packet (self->device,
630 : : packet,
631 : : NULL,
632 : : (GAsyncReadyCallback) valent_device_send_packet_cb,
633 : : NULL);
634 : : }
635 : :
636 : : /*< private >
637 : : * @self: a `ValentSmsDevice`
638 : : * @range_start_timestamp: the timestamp of the newest message to request
639 : : * @number_to_request: the maximum number of messages to return
640 : : *
641 : : * Send a request for messages starting at @range_start_timestamp in
642 : : * oldest-to-newest order, for a maximum of @number_to_request.
643 : : */
644 : : static void
645 : 3 : valent_sms_device_request_conversation (ValentSmsDevice *self,
646 : : int64_t thread_id,
647 : : int64_t range_start_timestamp,
648 : : int64_t number_to_request)
649 : : {
650 : 3 : g_autoptr (JsonBuilder) builder = NULL;
651 [ - + - - ]: 3 : g_autoptr (JsonNode) packet = NULL;
652 : :
653 [ + - ]: 3 : g_return_if_fail (VALENT_IS_SMS_DEVICE (self));
654 [ - + ]: 3 : g_return_if_fail (thread_id >= 0);
655 : :
656 : 3 : valent_packet_init (&builder, "kdeconnect.sms.request_conversation");
657 : 3 : json_builder_set_member_name (builder, "threadID");
658 : 3 : json_builder_add_int_value (builder, thread_id);
659 : :
660 [ + - ]: 3 : if (range_start_timestamp > 0)
661 : : {
662 : 3 : json_builder_set_member_name (builder, "rangeStartTimestamp");
663 : 3 : json_builder_add_int_value (builder, range_start_timestamp);
664 : : }
665 : :
666 [ + - ]: 3 : if (number_to_request > 0)
667 : : {
668 : 3 : json_builder_set_member_name (builder, "numberToRequest");
669 : 3 : json_builder_add_int_value (builder, number_to_request);
670 : : }
671 : :
672 : 3 : packet = valent_packet_end (&builder);
673 [ + - ]: 3 : valent_device_send_packet (self->device,
674 : : packet,
675 : : self->cancellable,
676 : : (GAsyncReadyCallback) valent_device_send_packet_cb,
677 : : NULL);
678 : : }
679 : :
680 : : static inline void
681 : 3 : valent_sms_device_request_conversations (ValentSmsDevice *self)
682 : : {
683 : 0 : g_autoptr (JsonNode) packet = NULL;
684 : :
685 [ + - ]: 3 : g_return_if_fail (VALENT_IS_SMS_DEVICE (self));
686 : :
687 : 3 : packet = valent_packet_new ("kdeconnect.sms.request_conversations");
688 [ + - ]: 3 : valent_device_send_packet (self->device,
689 : : packet,
690 : : self->cancellable,
691 : : (GAsyncReadyCallback) valent_device_send_packet_cb,
692 : : NULL);
693 : : }
694 : :
695 : : /*
696 : : * ValentMessagesAdapter
697 : : */
698 : : static JsonNode *
699 : 0 : valent_message_to_packet (ValentMessage *message,
700 : : GError **error)
701 : : {
702 : 0 : g_autoptr (JsonBuilder) builder = NULL;
703 : 0 : const char * const *recipients = NULL;
704 : 0 : GListModel *attachments = NULL;
705 : 0 : unsigned int n_attachments = 0;;
706 : 0 : int64_t sub_id = -1;
707 : 0 : const char *text;
708 : :
709 [ # # ]: 0 : g_return_val_if_fail (VALENT_IS_MESSAGE (message), FALSE);
710 [ # # # # ]: 0 : g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
711 : :
712 : 0 : attachments = valent_message_get_attachments (message);
713 [ # # ]: 0 : if (attachments != NULL)
714 : 0 : n_attachments = g_list_model_get_n_items (attachments);
715 : :
716 : 0 : recipients = valent_message_get_recipients (message);
717 [ # # ]: 0 : if (recipients == NULL)
718 : : {
719 : 0 : g_set_error_literal (error,
720 : : G_IO_ERROR,
721 : : G_IO_ERROR_FAILED,
722 : : "message has no recipients");
723 : 0 : return NULL;
724 : : }
725 : :
726 : : // Build the packet
727 : 0 : valent_packet_init (&builder, "kdeconnect.sms.request");
728 : :
729 : 0 : json_builder_set_member_name (builder, "addresses");
730 : 0 : json_builder_begin_array (builder);
731 [ # # ]: 0 : for (size_t i = 0; recipients[i] != NULL; i++)
732 : : {
733 : 0 : json_builder_begin_object (builder);
734 : 0 : json_builder_set_member_name (builder, "address");
735 : 0 : json_builder_add_string_value (builder, recipients[i]);
736 : 0 : json_builder_end_object (builder);
737 : : }
738 : 0 : json_builder_end_array (builder);
739 : :
740 : 0 : text = valent_message_get_text (message);
741 : 0 : json_builder_set_member_name (builder, "messageBody");
742 : 0 : json_builder_add_string_value (builder, text);
743 : :
744 : 0 : json_builder_set_member_name (builder, "attachments");
745 : 0 : json_builder_begin_array (builder);
746 [ # # ]: 0 : for (unsigned int i = 0; i < n_attachments; i++)
747 : : {
748 : 0 : g_autoptr (ValentMessageAttachment) attachment = NULL;
749 : 0 : GFile *file;
750 [ # # # # ]: 0 : g_autofree char *basename = NULL;
751 : 0 : g_autofree char *mimetype = NULL;
752 : 0 : g_autofree unsigned char *data = NULL;
753 : 0 : size_t len;
754 : 0 : g_autofree char *encoded_file = NULL;
755 : 0 : g_autoptr (GError) warn = NULL;
756 : :
757 : 0 : attachment = g_list_model_get_item (attachments, i);
758 : 0 : file = valent_message_attachment_get_file (attachment);
759 : 0 : basename = g_file_get_basename (file);
760 : :
761 : : // FIXME: async
762 [ # # ]: 0 : if (!g_file_load_contents (file, NULL, (char **)&data, &len, NULL, &warn))
763 : : {
764 : 0 : g_debug ("Failed to load attachment \"%s\"", basename);
765 [ # # ]: 0 : continue;
766 : : }
767 : :
768 : 0 : encoded_file = g_base64_encode (data, len);
769 : 0 : mimetype = g_content_type_guess (basename, data, len, NULL /* uncertain */);
770 : :
771 : 0 : json_builder_begin_object (builder);
772 : 0 : json_builder_set_member_name (builder, "fileName");
773 : 0 : json_builder_add_string_value (builder, basename);
774 : 0 : json_builder_set_member_name (builder, "mimeType");
775 : 0 : json_builder_add_string_value (builder, mimetype);
776 : 0 : json_builder_set_member_name (builder, "base64EncodedFile");
777 : 0 : json_builder_add_string_value (builder, encoded_file);
778 [ # # ]: 0 : json_builder_end_object (builder);
779 : : }
780 : 0 : json_builder_end_array (builder);
781 : :
782 : 0 : sub_id = valent_message_get_subscription_id (message);
783 : 0 : json_builder_set_member_name (builder, "subID");
784 : 0 : json_builder_add_int_value (builder, sub_id);
785 : :
786 : 0 : json_builder_set_member_name (builder, "version");
787 : 0 : json_builder_add_int_value (builder, 2);
788 : :
789 : 0 : return valent_packet_end (&builder);
790 : : }
791 : :
792 : : static void
793 : 0 : valent_sms_device_send_message_cb (ValentDevice *device,
794 : : GAsyncResult *result,
795 : : gpointer user_data)
796 : : {
797 : 0 : g_autoptr (GTask) task = G_TASK (g_steal_pointer (&user_data));
798 : 0 : GError *error = NULL;
799 : :
800 [ # # ]: 0 : if (valent_device_send_packet_finish (device, result, &error))
801 : 0 : g_task_return_boolean (task, TRUE);
802 : : else
803 : 0 : g_task_return_error (task, g_steal_pointer (&error));
804 : 0 : }
805 : :
806 : : static void
807 : 0 : valent_sms_device_send_message (ValentMessagesAdapter *adapter,
808 : : ValentMessage *message,
809 : : GCancellable *cancellable,
810 : : GAsyncReadyCallback callback,
811 : : gpointer user_data)
812 : : {
813 : 0 : ValentSmsDevice *self = VALENT_SMS_DEVICE (adapter);
814 : 0 : g_autoptr (GTask) task = NULL;
815 [ # # ]: 0 : g_autoptr (JsonNode) packet = NULL;
816 : 0 : GError *error = NULL;
817 : :
818 [ # # ]: 0 : g_assert (VALENT_IS_SMS_DEVICE (self));
819 [ # # ]: 0 : g_assert (VALENT_IS_MESSAGE (message));
820 [ # # # # : 0 : g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
# # # # ]
821 : :
822 : 0 : packet = valent_message_to_packet (message, &error);
823 [ # # ]: 0 : if (packet == NULL)
824 : : {
825 : 0 : g_task_report_error (adapter, callback, user_data,
826 : : valent_messages_adapter_send_message,
827 : 0 : g_steal_pointer (&error));
828 : 0 : return;
829 : : }
830 : :
831 : 0 : task = g_task_new (adapter, cancellable, callback, user_data);
832 [ # # ]: 0 : g_task_set_source_tag (task, valent_sms_device_send_message);
833 : 0 : g_task_set_task_data (task, g_object_ref (message), g_object_unref);
834 : :
835 : 0 : valent_device_send_packet (self->device,
836 : : packet,
837 : : cancellable, // TODO: chained cancellable
838 : : (GAsyncReadyCallback)valent_sms_device_send_message_cb,
839 : : g_object_ref (task));
840 : : }
841 : :
842 : : static void
843 : 9 : on_device_state_changed (ValentDevice *device,
844 : : GParamSpec *pspec,
845 : : ValentSmsDevice *self)
846 : : {
847 : 9 : ValentDeviceState state = VALENT_DEVICE_STATE_NONE;
848 : 9 : gboolean available;
849 : :
850 : 9 : state = valent_device_get_state (device);
851 : 9 : available = (state & VALENT_DEVICE_STATE_CONNECTED) != 0 &&
852 : : (state & VALENT_DEVICE_STATE_PAIRED) != 0;
853 : :
854 [ + + + - ]: 9 : if (available && self->cancellable == NULL)
855 : : {
856 : 12 : g_autoptr (GCancellable) cancellable = NULL;
857 : :
858 : 3 : cancellable = g_cancellable_new ();
859 : 3 : self->cancellable = valent_object_chain_cancellable (VALENT_OBJECT (self),
860 : : cancellable);
861 [ + - ]: 3 : valent_sms_device_request_conversations (self);
862 : : }
863 [ + + ]: 6 : else if (!available && self->cancellable != NULL)
864 : : {
865 : 3 : g_cancellable_cancel (self->cancellable);
866 [ + - ]: 3 : g_clear_object (&self->cancellable);
867 : : }
868 : 9 : }
869 : :
870 : : /*
871 : : * GObject
872 : : */
873 : : static void
874 : 3 : valent_sms_device_constructed (GObject *object)
875 : : {
876 : 3 : ValentSmsDevice *self = VALENT_SMS_DEVICE (object);
877 : :
878 : 3 : G_OBJECT_CLASS (valent_sms_device_parent_class)->constructed (object);
879 : :
880 : 3 : self->device = valent_resource_get_source (VALENT_RESOURCE (self));
881 : 3 : g_signal_connect_object (self->device,
882 : : "notify::state",
883 : : G_CALLBACK (on_device_state_changed),
884 : : self,
885 : : G_CONNECT_DEFAULT);
886 : :
887 : 3 : g_object_get (self, "connection", &self->connection, NULL);
888 [ + - ]: 3 : g_assert (TRACKER_IS_SPARQL_CONNECTION (self->connection));
889 : 3 : }
890 : :
891 : : static void
892 : 3 : valent_sms_device_finalize (GObject *object)
893 : : {
894 : 3 : ValentSmsDevice *self = VALENT_SMS_DEVICE (object);
895 : :
896 : 3 : g_queue_clear_full (&self->attachment_requests, attachment_request_free);
897 [ + - ]: 3 : g_clear_pointer (&self->message_requests, g_ptr_array_unref);
898 [ - + ]: 3 : g_clear_object (&self->cancellable);
899 [ + - ]: 3 : g_clear_object (&self->connection);
900 [ + + ]: 3 : g_clear_object (&self->get_timestamp_stmt);
901 : :
902 : 3 : G_OBJECT_CLASS (valent_sms_device_parent_class)->finalize (object);
903 : 3 : }
904 : :
905 : : static void
906 : 1 : valent_sms_device_class_init (ValentSmsDeviceClass *klass)
907 : : {
908 : 1 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
909 : 1 : ValentMessagesAdapterClass *adapter_class = VALENT_MESSAGES_ADAPTER_CLASS (klass);
910 : :
911 : 1 : object_class->constructed = valent_sms_device_constructed;
912 : 1 : object_class->finalize = valent_sms_device_finalize;
913 : :
914 : 1 : adapter_class->send_message = valent_sms_device_send_message;
915 : : }
916 : :
917 : : static void
918 : 3 : valent_sms_device_init (ValentSmsDevice *self)
919 : : {
920 : 3 : g_queue_init (&self->attachment_requests);
921 : 3 : self->message_requests = g_ptr_array_new_with_free_func (message_request_free);
922 : 3 : }
923 : :
924 : : /**
925 : : * valent_sms_device_new:
926 : : * @device: a `ValentDevice`
927 : : *
928 : : * Create a new `ValentSmsDevice`.
929 : : *
930 : : * Returns: (transfer full): a new message store
931 : : */
932 : : ValentMessagesAdapter *
933 : 3 : valent_sms_device_new (ValentDevice *device)
934 : : {
935 : 6 : g_autoptr (ValentContext) context = NULL;
936 [ + - ]: 3 : g_autofree char *iri = NULL;
937 : :
938 [ + - ]: 3 : g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
939 : :
940 : 3 : context = valent_context_new (valent_device_get_context (device),
941 : : "plugin",
942 : : "sms");
943 : 3 : iri = tracker_sparql_escape_uri_printf ("urn:valent:messages:%s",
944 : : valent_device_get_id (device));
945 : 3 : return g_object_new (VALENT_TYPE_SMS_DEVICE,
946 : : "iri", iri,
947 : : "context", context,
948 : : "source", device,
949 : : "title", valent_device_get_name (device),
950 : : NULL);
951 : : }
952 : :
953 : : static void
954 : 3 : cursor_get_timestamp_cb (TrackerSparqlCursor *cursor,
955 : : GAsyncResult *result,
956 : : gpointer user_data)
957 : : {
958 : 3 : g_autoptr (GTask) task = G_TASK (g_steal_pointer (&user_data));
959 [ + - ]: 3 : g_autoptr (GDateTime) datetime = NULL;
960 [ - + ]: 6 : g_autofree int64_t *timestamp = g_new0 (int64_t, 1);
961 : 3 : GError *error = NULL;
962 : :
963 [ - + + - ]: 6 : if (tracker_sparql_cursor_next_finish (cursor, result, &error) &&
964 : 3 : tracker_sparql_cursor_is_bound (cursor, 0))
965 : : {
966 : 0 : datetime = tracker_sparql_cursor_get_datetime (cursor, 0);
967 : 0 : *timestamp = g_date_time_to_unix_usec (datetime) / 1000;
968 : : }
969 : 3 : tracker_sparql_cursor_close (cursor);
970 : :
971 [ + - ]: 3 : if (error == NULL)
972 : 3 : g_task_return_pointer (task, g_steal_pointer (×tamp), g_free);
973 : : else
974 : 0 : g_task_return_error (task, g_steal_pointer (&error));
975 : :
976 : 3 : g_free (timestamp);
977 : 3 : }
978 : :
979 : : static void
980 : 3 : execute_get_timestamp_cb (TrackerSparqlStatement *stmt,
981 : : GAsyncResult *result,
982 : : gpointer user_data)
983 : : {
984 : 6 : g_autoptr (GTask) task = G_TASK (g_steal_pointer (&user_data));
985 [ - - + - ]: 3 : g_autoptr (TrackerSparqlCursor) cursor = NULL;
986 : 3 : GCancellable *cancellable = NULL;
987 [ - - ]: 3 : g_autoptr (GError) error = NULL;
988 : :
989 : 3 : cursor = tracker_sparql_statement_execute_finish (stmt, result, &error);
990 [ - + ]: 3 : if (cursor == NULL)
991 : : {
992 : 0 : g_task_return_error (task, g_steal_pointer (&error));
993 [ # # ]: 0 : return;
994 : : }
995 : :
996 : 3 : cancellable = g_task_get_cancellable (G_TASK (result));
997 [ - + ]: 3 : tracker_sparql_cursor_next_async (cursor,
998 : : cancellable,
999 : : (GAsyncReadyCallback) cursor_get_timestamp_cb,
1000 : : g_object_ref (task));
1001 : : }
1002 : :
1003 : : static void
1004 : 3 : valent_sms_device_get_timestamp (ValentSmsDevice *self,
1005 : : int64_t thread_id,
1006 : : GCancellable *cancellable,
1007 : : GAsyncReadyCallback callback,
1008 : : gpointer user_data)
1009 : : {
1010 : 3 : g_autoptr (GTask) task = NULL;
1011 : 3 : GError *error = NULL;
1012 : :
1013 [ + - ]: 3 : g_return_if_fail (VALENT_IS_MESSAGES_ADAPTER (self));
1014 [ - + ]: 3 : g_return_if_fail (thread_id >= 0);
1015 [ + - + - : 3 : g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
- + - - ]
1016 : :
1017 : 3 : task = g_task_new (self, cancellable, callback, user_data);
1018 [ + - ]: 3 : g_task_set_source_tag (task, valent_sms_device_get_timestamp);
1019 : :
1020 [ + + ]: 3 : if (self->get_timestamp_stmt == NULL)
1021 : : {
1022 : 2 : self->get_timestamp_stmt =
1023 : 2 : tracker_sparql_connection_load_statement_from_gresource (self->connection,
1024 : : GET_TIMESTAMP_RQ,
1025 : : cancellable,
1026 : : &error);
1027 : : }
1028 : :
1029 [ - + ]: 3 : if (self->get_timestamp_stmt == NULL)
1030 : : {
1031 : 0 : g_task_return_error (task, g_steal_pointer (&error));
1032 [ # # ]: 0 : return;
1033 : : }
1034 : :
1035 : 3 : tracker_sparql_statement_bind_int (self->get_timestamp_stmt,
1036 : : "threadId",
1037 : : thread_id);
1038 [ + - ]: 3 : tracker_sparql_statement_execute_async (self->get_timestamp_stmt,
1039 : : cancellable,
1040 : : (GAsyncReadyCallback) execute_get_timestamp_cb,
1041 : : g_object_ref (task));
1042 : : }
1043 : :
1044 : : static int64_t
1045 : 3 : valent_sms_device_get_timestamp_finish (ValentSmsDevice *store,
1046 : : GAsyncResult *result,
1047 : : GError **error)
1048 : : {
1049 : 6 : g_autofree int64_t *ret = NULL;
1050 : :
1051 [ + - ]: 3 : g_return_val_if_fail (VALENT_IS_MESSAGES_ADAPTER (store), 0);
1052 [ - + ]: 3 : g_return_val_if_fail (g_task_is_valid (result, store), 0);
1053 [ + - - + ]: 3 : g_return_val_if_fail (error == NULL || *error == NULL, 0);
1054 : :
1055 : 3 : ret = g_task_propagate_pointer (G_TASK (result), error);
1056 : :
1057 [ + - ]: 3 : return ret != NULL ? *ret : 0;
1058 : : }
1059 : :
1060 : : typedef struct
1061 : : {
1062 : : ValentSmsDevice *self;
1063 : : JsonNode *pending;
1064 : : int64_t thread_id;
1065 : : int64_t start_date;
1066 : : int64_t end_date;
1067 : : int64_t max_results;
1068 : : } MessageRequest;
1069 : :
1070 : : #define DEFAULT_MESSAGE_REQUEST (100)
1071 : :
1072 : 0 : G_DEFINE_AUTOPTR_CLEANUP_FUNC (MessageRequest, message_request_free)
1073 : :
1074 : : static void
1075 : 3 : message_request_free (gpointer data)
1076 : : {
1077 : 3 : MessageRequest *request = data;
1078 : :
1079 [ + - ]: 3 : g_clear_pointer (&request->pending, json_node_unref);
1080 : 3 : g_free (request);
1081 : 3 : }
1082 : :
1083 : : static gboolean
1084 : 3 : find_message_request (gconstpointer a,
1085 : : gconstpointer b)
1086 : : {
1087 : 3 : return ((MessageRequest *)a)->thread_id == *((int64_t *)b);
1088 : : }
1089 : :
1090 : : static void
1091 : 6 : find_message_range (JsonArray *messages,
1092 : : int64_t *out_thread_id,
1093 : : int64_t *out_start_date,
1094 : : int64_t *out_end_date)
1095 : : {
1096 : 6 : unsigned int n_messages;
1097 : :
1098 [ + - ]: 6 : g_assert (messages != NULL);
1099 [ + - - + ]: 6 : g_assert (out_thread_id && out_start_date && out_end_date);
1100 : :
1101 : 6 : *out_thread_id = 0;
1102 : 6 : *out_start_date = INT64_MAX;
1103 : 6 : *out_end_date = 0;
1104 : :
1105 : 6 : n_messages = json_array_get_length (messages);
1106 [ + + ]: 15 : for (unsigned int i = 0; i < n_messages; i++)
1107 : : {
1108 : 9 : JsonObject *message = json_array_get_object_element (messages, i);
1109 : 9 : int64_t date = json_object_get_int_member (message, "date");
1110 : :
1111 [ + + ]: 9 : if (*out_thread_id == 0)
1112 : 6 : *out_thread_id = json_object_get_int_member (message, "thread_id");
1113 : :
1114 [ + + ]: 9 : if (*out_start_date > date)
1115 : 6 : *out_start_date = date;
1116 : :
1117 [ + - ]: 9 : if (*out_end_date < date)
1118 : 9 : *out_end_date = date;
1119 : : }
1120 : 6 : }
1121 : :
1122 : : static void
1123 : 3 : valent_sms_device_get_timestamp_cb (ValentSmsDevice *self,
1124 : : GAsyncResult *result,
1125 : : gpointer user_data)
1126 : : {
1127 : 6 : g_autoptr (MessageRequest) request = g_steal_pointer (&user_data);
1128 : 3 : int64_t cache_date;
1129 [ - - - + ]: 3 : g_autoptr (GError) error = NULL;
1130 : :
1131 : 3 : cache_date = valent_sms_device_get_timestamp_finish (self, result, &error);
1132 [ - + ]: 3 : if (error != NULL)
1133 : : {
1134 [ # # ]: 0 : if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1135 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
1136 : :
1137 [ # # ]: 0 : return;
1138 : : }
1139 : :
1140 [ + - ]: 3 : if (cache_date < request->end_date)
1141 : : {
1142 [ + - ]: 3 : if (request->pending != NULL)
1143 : 3 : valent_sms_device_add_json (self, request->pending);
1144 : :
1145 : 3 : request->start_date = cache_date;
1146 : 3 : valent_sms_device_request_conversation (self,
1147 : : request->thread_id,
1148 : : request->end_date,
1149 : : request->max_results);
1150 : 3 : g_ptr_array_add (self->message_requests, g_steal_pointer (&request));
1151 : : }
1152 : : }
1153 : :
1154 : : /**
1155 : : * valent_sms_device_handle_messages:
1156 : : * @self: a `ValentSmsDevice`
1157 : : * @packet: a `kdeconnect.sms.messages` packet
1158 : : *
1159 : : * Handle a packet of messages.
1160 : : */
1161 : : void
1162 : 6 : valent_sms_device_handle_messages (ValentSmsDevice *self,
1163 : : JsonNode *packet)
1164 : : {
1165 : 12 : g_autofree char *thread_iri = NULL;
1166 : 6 : JsonNode *node;
1167 : 6 : JsonObject *body;
1168 : 6 : JsonArray *messages;
1169 : 6 : unsigned int n_messages = 0;
1170 : 6 : int64_t thread_id;
1171 : 6 : int64_t start_date = 0, end_date = 0;
1172 : 6 : unsigned int index_ = 0;
1173 : :
1174 : 6 : VALENT_ENTRY;
1175 : :
1176 [ + - ]: 6 : g_assert (VALENT_IS_SMS_DEVICE (self));
1177 [ - + ]: 6 : g_assert (VALENT_IS_PACKET (packet));
1178 : :
1179 : 6 : body = valent_packet_get_body (packet);
1180 : 6 : node = json_object_get_member (body, "messages");
1181 [ + - - + ]: 6 : if (node == NULL || !JSON_NODE_HOLDS_ARRAY (node))
1182 : : {
1183 : 0 : g_warning ("%s(): expected \"messages\" field holding an array", G_STRFUNC);
1184 : 0 : return;
1185 : : }
1186 : :
1187 : : /* It's not clear if this could ever happen, or what it would imply if it did,
1188 : : * so log a debug message and bail.
1189 : : */
1190 : 6 : messages = json_node_get_array (node);
1191 : 6 : n_messages = json_array_get_length (messages);
1192 [ - + ]: 6 : if (n_messages == 0)
1193 : : {
1194 : 0 : g_debug ("%s(): expected \"messages\" field holding an array of objects",
1195 : : G_STRFUNC);
1196 : 0 : return;
1197 : : }
1198 : :
1199 : 6 : thread_id = json_object_get_int_member (json_array_get_object_element (messages, 0),
1200 : : "thread_id");
1201 : 6 : thread_iri = g_strdup_printf ("urn:valent:messages:%s:%"PRId64,
1202 : : valent_device_get_id (self->device),
1203 : : thread_id);
1204 : :
1205 : : /* Check if there is an active request for this thread
1206 : : */
1207 : 6 : find_message_range (messages, &thread_id, &start_date, &end_date);
1208 [ + + ]: 6 : if (g_ptr_array_find_with_equal_func (self->message_requests,
1209 : : &thread_id,
1210 : : find_message_request,
1211 : : &index_))
1212 : : {
1213 : 3 : MessageRequest *request = g_ptr_array_index (self->message_requests, index_);
1214 : :
1215 : : /* This is a response to our request
1216 : : */
1217 [ + + ]: 3 : if (request->end_date == end_date)
1218 : : {
1219 [ - + ]: 1 : if (n_messages >= request->max_results &&
1220 [ # # ]: 0 : request->start_date < start_date)
1221 : : {
1222 : 0 : request->end_date = start_date;
1223 : 0 : valent_sms_device_request_conversation (self,
1224 : : request->thread_id,
1225 : : request->end_date,
1226 : : request->max_results);
1227 : : }
1228 : : else
1229 : : {
1230 : 1 : g_ptr_array_remove_index (self->message_requests, index_);
1231 : : }
1232 : : }
1233 : :
1234 : : // FIXME: only store expected responses
1235 : 3 : valent_sms_device_add_json (self, node);
1236 : : }
1237 [ - + ]: 3 : else if (n_messages == 1)
1238 : : {
1239 : 3 : MessageRequest *request;
1240 : :
1241 : 3 : request = g_new0 (MessageRequest, 1);
1242 : 3 : request->pending = json_node_ref (node);
1243 : 3 : request->thread_id = thread_id;
1244 : 3 : request->start_date = end_date;
1245 : 3 : request->end_date = end_date;
1246 : 3 : request->max_results = DEFAULT_MESSAGE_REQUEST;
1247 : :
1248 : 3 : valent_sms_device_get_timestamp (self,
1249 : : thread_id,
1250 : : self->cancellable,
1251 : : (GAsyncReadyCallback) valent_sms_device_get_timestamp_cb,
1252 : : g_steal_pointer (&request));
1253 : : }
1254 : :
1255 : 6 : VALENT_EXIT;
1256 : : }
1257 : :
1258 : : /**
1259 : : * valent_sms_device_handle_attachment_file:
1260 : : * @self: a `ValentSmsDevice`
1261 : : * @packet: a `kdeconnect.sms.attachment_file` packet
1262 : : *
1263 : : * Handle an attachment file.
1264 : : */
1265 : : void
1266 : 1 : valent_sms_device_handle_attachment_file (ValentSmsDevice *self,
1267 : : JsonNode *packet)
1268 : : {
1269 : 1 : ValentContext *context = NULL;
1270 : 1 : g_autoptr (ValentTransfer) transfer = NULL;
1271 [ + - ]: 1 : g_autoptr (GFile) file = NULL;
1272 : 1 : const char *filename = NULL;
1273 : :
1274 [ + - ]: 1 : g_assert (VALENT_IS_SMS_DEVICE (self));
1275 [ - + ]: 1 : g_assert (VALENT_IS_PACKET (packet));
1276 : :
1277 [ - + ]: 1 : if (!valent_packet_get_string (packet, "filename", &filename))
1278 : : {
1279 : 0 : g_warning ("%s(): expected \"filename\" field holding a string",
1280 : : G_STRFUNC);
1281 : 0 : return;
1282 : : }
1283 : :
1284 : 1 : context = valent_extension_get_context (VALENT_EXTENSION (self));
1285 : 1 : file = valent_context_get_cache_file (context, filename);
1286 : 1 : transfer = valent_device_transfer_new (self->device, packet, file);
1287 [ + - ]: 1 : valent_transfer_execute (transfer,
1288 : : self->cancellable,
1289 : : (GAsyncReadyCallback) handle_attachment_file_cb,
1290 : : self);
1291 : : }
1292 : :
|