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_queue (ValentSmsDevice *self,
32 : : const char *iri,
33 : : int64_t part_id,
34 : : const char *unique_identifier);
35 : : static void valent_sms_device_request_attachment (ValentSmsDevice *self,
36 : : int64_t thread_id,
37 : : const char *unique_identifier);
38 : : static void valent_sms_device_request_conversation (ValentSmsDevice *self,
39 : : int64_t thread_id,
40 : : int64_t range_start_timestamp,
41 : : int64_t number_to_request);
42 : :
43 [ + + + - ]: 27 : G_DEFINE_FINAL_TYPE (ValentSmsDevice, valent_sms_device, VALENT_TYPE_MESSAGES_ADAPTER)
44 : :
45 : :
46 : : typedef struct
47 : : {
48 : : char *iri;
49 : : int64_t part_id;
50 : : char *unique_identifier;
51 : : } AttachmentRequest;
52 : :
53 : : static void attachment_request_next (ValentSmsDevice *self);
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 : g_autofree 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_object_dup_iri (VALENT_OBJECT (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 : :
674 [ + - ]: 3 : valent_device_send_packet (self->device,
675 : : packet,
676 : : NULL,
677 : : (GAsyncReadyCallback) valent_device_send_packet_cb,
678 : : NULL);
679 : : }
680 : :
681 : : static inline void
682 : 3 : valent_sms_device_request_conversations (ValentSmsDevice *self)
683 : : {
684 : 3 : g_autoptr (JsonBuilder) builder = NULL;
685 [ - - - + ]: 3 : g_autoptr (JsonNode) packet = NULL;
686 : :
687 [ + - - - ]: 3 : g_return_if_fail (VALENT_IS_SMS_DEVICE (self));
688 : :
689 : 3 : valent_packet_init (&builder, "kdeconnect.sms.request_conversations");
690 : 3 : packet = valent_packet_end (&builder);
691 : :
692 [ + - ]: 3 : valent_device_send_packet (self->device,
693 : : packet,
694 : : NULL,
695 : : (GAsyncReadyCallback) valent_device_send_packet_cb,
696 : : NULL);
697 : : }
698 : :
699 : : /*
700 : : * ValentMessagesAdapter
701 : : */
702 : : static JsonNode *
703 : 0 : valent_message_to_packet (ValentMessage *message,
704 : : GError **error)
705 : : {
706 : 0 : g_autoptr (JsonBuilder) builder = NULL;
707 : 0 : const char * const *recipients = NULL;
708 : 0 : GListModel *attachments = NULL;
709 : 0 : unsigned int n_attachments = 0;;
710 : 0 : int64_t sub_id = -1;
711 : 0 : const char *text;
712 : :
713 [ # # ]: 0 : g_return_val_if_fail (VALENT_IS_MESSAGE (message), FALSE);
714 [ # # # # ]: 0 : g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
715 : :
716 : 0 : attachments = valent_message_get_attachments (message);
717 [ # # ]: 0 : if (attachments != NULL)
718 : 0 : n_attachments = g_list_model_get_n_items (attachments);
719 : :
720 : 0 : recipients = valent_message_get_recipients (message);
721 [ # # ]: 0 : if (recipients == NULL)
722 : : {
723 : 0 : g_set_error_literal (error,
724 : : G_IO_ERROR,
725 : : G_IO_ERROR_FAILED,
726 : : "message has no recipients");
727 : 0 : return NULL;
728 : : }
729 : :
730 : : // Build the packet
731 : 0 : valent_packet_init (&builder, "kdeconnect.sms.request");
732 : :
733 : 0 : json_builder_set_member_name (builder, "addresses");
734 : 0 : json_builder_begin_array (builder);
735 [ # # ]: 0 : for (size_t i = 0; recipients[i] != NULL; i++)
736 : : {
737 : 0 : json_builder_begin_object (builder);
738 : 0 : json_builder_set_member_name (builder, "address");
739 : 0 : json_builder_add_string_value (builder, recipients[i]);
740 : 0 : json_builder_end_object (builder);
741 : : }
742 : 0 : json_builder_end_array (builder);
743 : :
744 : 0 : text = valent_message_get_text (message);
745 : 0 : json_builder_set_member_name (builder, "messageBody");
746 : 0 : json_builder_add_string_value (builder, text);
747 : :
748 : 0 : json_builder_set_member_name (builder, "attachments");
749 : 0 : json_builder_begin_array (builder);
750 [ # # ]: 0 : for (unsigned int i = 0; i < n_attachments; i++)
751 : : {
752 : 0 : g_autoptr (ValentMessageAttachment) attachment = NULL;
753 : 0 : GFile *file;
754 [ # # # # ]: 0 : g_autofree char *basename = NULL;
755 : 0 : g_autofree char *mimetype = NULL;
756 : 0 : g_autofree unsigned char *data = NULL;
757 : 0 : size_t len;
758 : 0 : g_autofree char *encoded_file = NULL;
759 : 0 : g_autoptr (GError) warn = NULL;
760 : :
761 : 0 : attachment = g_list_model_get_item (attachments, i);
762 : 0 : file = valent_message_attachment_get_file (attachment);
763 : 0 : basename = g_file_get_basename (file);
764 : :
765 : : // FIXME: async
766 [ # # ]: 0 : if (!g_file_load_contents (file, NULL, (char **)&data, &len, NULL, &warn))
767 : : {
768 : 0 : g_debug ("Failed to load attachment \"%s\"", basename);
769 [ # # ]: 0 : continue;
770 : : }
771 : :
772 : 0 : encoded_file = g_base64_encode (data, len);
773 : 0 : mimetype = g_content_type_guess (basename, data, len, NULL /* uncertain */);
774 : :
775 : 0 : json_builder_begin_object (builder);
776 : 0 : json_builder_set_member_name (builder, "fileName");
777 : 0 : json_builder_add_string_value (builder, basename);
778 : 0 : json_builder_set_member_name (builder, "mimeType");
779 : 0 : json_builder_add_string_value (builder, mimetype);
780 : 0 : json_builder_set_member_name (builder, "base64EncodedFile");
781 : 0 : json_builder_add_string_value (builder, encoded_file);
782 [ # # ]: 0 : json_builder_end_object (builder);
783 : : }
784 : 0 : json_builder_end_array (builder);
785 : :
786 : 0 : sub_id = valent_message_get_subscription_id (message);
787 : 0 : json_builder_set_member_name (builder, "subID");
788 : 0 : json_builder_add_int_value (builder, sub_id);
789 : :
790 : 0 : json_builder_set_member_name (builder, "version");
791 : 0 : json_builder_add_int_value (builder, 2);
792 : :
793 : 0 : return valent_packet_end (&builder);
794 : : }
795 : :
796 : : static void
797 : 0 : valent_sms_device_send_message_cb (ValentDevice *device,
798 : : GAsyncResult *result,
799 : : gpointer user_data)
800 : : {
801 : 0 : g_autoptr (GTask) task = G_TASK (g_steal_pointer (&user_data));
802 : 0 : GError *error = NULL;
803 : :
804 [ # # ]: 0 : if (valent_device_send_packet_finish (device, result, &error))
805 : 0 : g_task_return_boolean (task, TRUE);
806 : : else
807 : 0 : g_task_return_error (task, g_steal_pointer (&error));
808 : 0 : }
809 : :
810 : : static void
811 : 0 : valent_sms_device_send_message (ValentMessagesAdapter *adapter,
812 : : ValentMessage *message,
813 : : GCancellable *cancellable,
814 : : GAsyncReadyCallback callback,
815 : : gpointer user_data)
816 : : {
817 : 0 : ValentSmsDevice *self = VALENT_SMS_DEVICE (adapter);
818 : 0 : g_autoptr (GTask) task = NULL;
819 [ # # ]: 0 : g_autoptr (JsonNode) packet = NULL;
820 : 0 : GError *error = NULL;
821 : :
822 [ # # ]: 0 : g_assert (VALENT_IS_SMS_DEVICE (self));
823 [ # # ]: 0 : g_assert (VALENT_IS_MESSAGE (message));
824 [ # # # # : 0 : g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
# # # # ]
825 : :
826 : 0 : packet = valent_message_to_packet (message, &error);
827 [ # # ]: 0 : if (packet == NULL)
828 : : {
829 : 0 : g_task_report_error (adapter, callback, user_data,
830 : : valent_messages_adapter_send_message,
831 : 0 : g_steal_pointer (&error));
832 : 0 : return;
833 : : }
834 : :
835 : 0 : task = g_task_new (adapter, cancellable, callback, user_data);
836 [ # # ]: 0 : g_task_set_source_tag (task, valent_sms_device_send_message);
837 : 0 : g_task_set_task_data (task, g_object_ref (message), g_object_unref);
838 : :
839 : 0 : valent_device_send_packet (self->device,
840 : : packet,
841 : : cancellable,
842 : : (GAsyncReadyCallback)valent_sms_device_send_message_cb,
843 : : g_object_ref (task));
844 : : }
845 : :
846 : : static void
847 : 9 : on_device_state_changed (ValentDevice *device,
848 : : GParamSpec *pspec,
849 : : ValentSmsDevice *self)
850 : : {
851 : 9 : ValentDeviceState state = VALENT_DEVICE_STATE_NONE;
852 : 9 : gboolean available;
853 : :
854 : 9 : state = valent_device_get_state (device);
855 : 9 : available = (state & VALENT_DEVICE_STATE_CONNECTED) != 0 &&
856 : : (state & VALENT_DEVICE_STATE_PAIRED) != 0;
857 : :
858 [ + + ]: 9 : if (available)
859 : 3 : valent_sms_device_request_conversations (self);
860 : 9 : }
861 : :
862 : : /*
863 : : * GObject
864 : : */
865 : : static void
866 : 3 : valent_sms_device_constructed (GObject *object)
867 : : {
868 : 3 : ValentSmsDevice *self = VALENT_SMS_DEVICE (object);
869 : :
870 : 3 : G_OBJECT_CLASS (valent_sms_device_parent_class)->constructed (object);
871 : :
872 : 3 : self->device = valent_extension_get_object (VALENT_EXTENSION (self));
873 : 3 : g_signal_connect_object (self->device,
874 : : "notify::state",
875 : : G_CALLBACK (on_device_state_changed),
876 : : self,
877 : : G_CONNECT_DEFAULT);
878 : :
879 : 3 : g_object_get (self,
880 : : "connection", &self->connection,
881 : : "cancellable", &self->cancellable,
882 : : NULL);
883 : 3 : }
884 : :
885 : : static void
886 : 3 : valent_sms_device_finalize (GObject *object)
887 : : {
888 : 3 : ValentSmsDevice *self = VALENT_SMS_DEVICE (object);
889 : :
890 : 3 : g_queue_clear_full (&self->attachment_requests, attachment_request_free);
891 [ + - ]: 3 : g_clear_pointer (&self->message_requests, g_ptr_array_unref);
892 [ + - ]: 3 : g_clear_object (&self->connection);
893 [ + + ]: 3 : g_clear_object (&self->get_timestamp_stmt);
894 : :
895 : 3 : G_OBJECT_CLASS (valent_sms_device_parent_class)->finalize (object);
896 : 3 : }
897 : :
898 : : static void
899 : 1 : valent_sms_device_class_init (ValentSmsDeviceClass *klass)
900 : : {
901 : 1 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
902 : 1 : ValentMessagesAdapterClass *adapter_class = VALENT_MESSAGES_ADAPTER_CLASS (klass);
903 : :
904 : 1 : object_class->constructed = valent_sms_device_constructed;
905 : 1 : object_class->finalize = valent_sms_device_finalize;
906 : :
907 : 1 : adapter_class->send_message = valent_sms_device_send_message;
908 : : }
909 : :
910 : : static void
911 : 3 : valent_sms_device_init (ValentSmsDevice *self)
912 : : {
913 : 3 : g_queue_init (&self->attachment_requests);
914 : 3 : self->message_requests = g_ptr_array_new_with_free_func (g_free);
915 : 3 : }
916 : :
917 : : /**
918 : : * valent_sms_device_new:
919 : : * @device: a `ValentDevice`
920 : : *
921 : : * Create a new `ValentSmsDevice`.
922 : : *
923 : : * Returns: (transfer full): a new message store
924 : : */
925 : : ValentMessagesAdapter *
926 : 3 : valent_sms_device_new (ValentDevice *device)
927 : : {
928 : 6 : g_autoptr (ValentContext) context = NULL;
929 [ + - ]: 3 : g_autofree char *iri = NULL;
930 : :
931 [ + - ]: 3 : g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
932 : :
933 : 3 : context = valent_context_new (valent_device_get_context (device),
934 : : "plugin",
935 : : "sms");
936 : 3 : iri = tracker_sparql_escape_uri_printf ("urn:valent:messages:%s",
937 : : valent_device_get_id (device));
938 : 3 : return g_object_new (VALENT_TYPE_SMS_DEVICE,
939 : : "iri", iri,
940 : : "object", device,
941 : : "context", context,
942 : : NULL);
943 : : }
944 : :
945 : : static void
946 : 3 : cursor_get_timestamp_cb (TrackerSparqlCursor *cursor,
947 : : GAsyncResult *result,
948 : : gpointer user_data)
949 : : {
950 : 3 : g_autoptr (GTask) task = G_TASK (g_steal_pointer (&user_data));
951 [ + - ]: 3 : g_autoptr (GDateTime) datetime = NULL;
952 [ - + ]: 6 : g_autofree int64_t *timestamp = g_new0 (int64_t, 1);
953 : 3 : GError *error = NULL;
954 : :
955 [ - + + - ]: 6 : if (tracker_sparql_cursor_next_finish (cursor, result, &error) &&
956 : 3 : tracker_sparql_cursor_is_bound (cursor, 0))
957 : : {
958 : 0 : datetime = tracker_sparql_cursor_get_datetime (cursor, 0);
959 : 0 : *timestamp = g_date_time_to_unix_usec (datetime) / 1000;
960 : : }
961 : 3 : tracker_sparql_cursor_close (cursor);
962 : :
963 [ + - ]: 3 : if (error == NULL)
964 : 3 : g_task_return_pointer (task, g_steal_pointer (×tamp), g_free);
965 : : else
966 : 0 : g_task_return_error (task, g_steal_pointer (&error));
967 : :
968 : 3 : g_free (timestamp);
969 : 3 : }
970 : :
971 : : static void
972 : 3 : execute_get_timestamp_cb (TrackerSparqlStatement *stmt,
973 : : GAsyncResult *result,
974 : : gpointer user_data)
975 : : {
976 : 6 : g_autoptr (GTask) task = G_TASK (g_steal_pointer (&user_data));
977 [ - - + - ]: 3 : g_autoptr (TrackerSparqlCursor) cursor = NULL;
978 : 3 : GCancellable *cancellable = NULL;
979 [ - - ]: 3 : g_autoptr (GError) error = NULL;
980 : :
981 : 3 : cursor = tracker_sparql_statement_execute_finish (stmt, result, &error);
982 [ - + ]: 3 : if (cursor == NULL)
983 : : {
984 : 0 : g_task_return_error (task, g_steal_pointer (&error));
985 [ # # ]: 0 : return;
986 : : }
987 : :
988 : 3 : cancellable = g_task_get_cancellable (G_TASK (result));
989 [ - + ]: 3 : tracker_sparql_cursor_next_async (cursor,
990 : : cancellable,
991 : : (GAsyncReadyCallback) cursor_get_timestamp_cb,
992 : : g_object_ref (task));
993 : : }
994 : :
995 : : static void
996 : 3 : valent_sms_device_get_timestamp (ValentSmsDevice *store,
997 : : int64_t thread_id,
998 : : GCancellable *cancellable,
999 : : GAsyncReadyCallback callback,
1000 : : gpointer user_data)
1001 : : {
1002 : 3 : g_autoptr (GTask) task = NULL;
1003 : 3 : GError *error = NULL;
1004 : :
1005 [ + - ]: 3 : g_return_if_fail (VALENT_IS_MESSAGES_ADAPTER (store));
1006 [ - + ]: 3 : g_return_if_fail (thread_id >= 0);
1007 [ + - + - : 3 : g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
- + - - ]
1008 : :
1009 : 3 : task = g_task_new (store, cancellable, callback, user_data);
1010 [ + - ]: 3 : g_task_set_source_tag (task, valent_sms_device_get_timestamp);
1011 : :
1012 [ + + ]: 3 : if (store->get_timestamp_stmt == NULL)
1013 : : {
1014 : 2 : store->get_timestamp_stmt =
1015 : 2 : tracker_sparql_connection_load_statement_from_gresource (store->connection,
1016 : : GET_TIMESTAMP_RQ,
1017 : : cancellable,
1018 : : &error);
1019 : : }
1020 : :
1021 [ - + ]: 3 : if (store->get_timestamp_stmt == NULL)
1022 : : {
1023 : 0 : g_task_return_error (task, g_steal_pointer (&error));
1024 [ # # ]: 0 : return;
1025 : : }
1026 : :
1027 : 3 : tracker_sparql_statement_bind_int (store->get_timestamp_stmt,
1028 : : "threadId",
1029 : : thread_id);
1030 [ + - ]: 3 : tracker_sparql_statement_execute_async (store->get_timestamp_stmt,
1031 : : cancellable,
1032 : : (GAsyncReadyCallback) execute_get_timestamp_cb,
1033 : : g_object_ref (task));
1034 : : }
1035 : :
1036 : : static int64_t
1037 : 3 : valent_sms_device_get_timestamp_finish (ValentSmsDevice *store,
1038 : : GAsyncResult *result,
1039 : : GError **error)
1040 : : {
1041 : 6 : g_autofree int64_t *ret = NULL;
1042 : :
1043 [ + - ]: 3 : g_return_val_if_fail (VALENT_IS_MESSAGES_ADAPTER (store), 0);
1044 [ - + ]: 3 : g_return_val_if_fail (g_task_is_valid (result, store), 0);
1045 [ + - - + ]: 3 : g_return_val_if_fail (error == NULL || *error == NULL, 0);
1046 : :
1047 : 3 : ret = g_task_propagate_pointer (G_TASK (result), error);
1048 : :
1049 [ + - ]: 3 : return ret != NULL ? *ret : 0;
1050 : : }
1051 : :
1052 : : typedef struct
1053 : : {
1054 : : ValentSmsDevice *self;
1055 : : int64_t thread_id;
1056 : : int64_t start_date;
1057 : : int64_t end_date;
1058 : : int64_t max_results;
1059 : : } RequestData;
1060 : :
1061 : : #define DEFAULT_MESSAGE_REQUEST (100)
1062 : :
1063 : : static gboolean
1064 : 3 : find_message_request (gconstpointer a,
1065 : : gconstpointer b)
1066 : : {
1067 : 3 : return ((RequestData *)a)->thread_id == *((int64_t *)b);
1068 : : }
1069 : :
1070 : : static void
1071 : 6 : find_message_range (JsonArray *messages,
1072 : : int64_t *out_thread_id,
1073 : : int64_t *out_start_date,
1074 : : int64_t *out_end_date)
1075 : : {
1076 : 6 : unsigned int n_messages;
1077 : :
1078 [ + - ]: 6 : g_assert (messages != NULL);
1079 [ + - - + ]: 6 : g_assert (out_thread_id && out_start_date && out_end_date);
1080 : :
1081 : 6 : *out_thread_id = 0;
1082 : 6 : *out_start_date = INT64_MAX;
1083 : 6 : *out_end_date = 0;
1084 : :
1085 : 6 : n_messages = json_array_get_length (messages);
1086 [ + + ]: 15 : for (unsigned int i = 0; i < n_messages; i++)
1087 : : {
1088 : 9 : JsonObject *message = json_array_get_object_element (messages, i);
1089 : 9 : int64_t date = json_object_get_int_member (message, "date");
1090 : :
1091 [ + + ]: 9 : if (*out_thread_id == 0)
1092 : 6 : *out_thread_id = json_object_get_int_member (message, "thread_id");
1093 : :
1094 [ + + ]: 9 : if (*out_start_date > date)
1095 : 6 : *out_start_date = date;
1096 : :
1097 [ + - ]: 9 : if (*out_end_date < date)
1098 : 9 : *out_end_date = date;
1099 : : }
1100 : 6 : }
1101 : :
1102 : : static void
1103 : 3 : valent_sms_device_get_timestamp_cb (ValentSmsDevice *self,
1104 : : GAsyncResult *result,
1105 : : gpointer user_data)
1106 : : {
1107 : 3 : g_autofree RequestData *request = (RequestData *)user_data;
1108 : 3 : int64_t cache_date;
1109 : 3 : g_autoptr (GError) error = NULL;
1110 : :
1111 : 3 : cache_date = valent_sms_device_get_timestamp_finish (self, result, &error);
1112 [ - + ]: 3 : if (error != NULL)
1113 : : {
1114 [ # # ]: 0 : if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1115 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
1116 : :
1117 [ # # ]: 0 : return;
1118 : : }
1119 : :
1120 [ + - ]: 3 : if (cache_date < request->end_date)
1121 : : {
1122 : 3 : request->start_date = cache_date;
1123 : 3 : valent_sms_device_request_conversation (self,
1124 : : request->thread_id,
1125 : : request->end_date,
1126 : : request->max_results);
1127 : 3 : g_ptr_array_add (self->message_requests, g_steal_pointer (&request));
1128 : : }
1129 : : }
1130 : :
1131 : : /**
1132 : : * valent_sms_device_handle_messages:
1133 : : * @self: a `ValentSmsDevice`
1134 : : * @packet: a `kdeconnect.sms.messages` packet
1135 : : *
1136 : : * Handle a packet of messages.
1137 : : */
1138 : : void
1139 : 6 : valent_sms_device_handle_messages (ValentSmsDevice *self,
1140 : : JsonNode *packet)
1141 : : {
1142 : 6 : ValentDevice *device = NULL;
1143 : 12 : g_autofree char *thread_iri = NULL;
1144 : 6 : JsonNode *node;
1145 : 6 : JsonObject *body;
1146 : 6 : JsonArray *messages;
1147 : 6 : unsigned int n_messages = 0;
1148 : 6 : int64_t thread_id;
1149 : 6 : int64_t start_date = 0, end_date = 0;
1150 : 6 : unsigned int index_ = 0;
1151 : :
1152 : 6 : VALENT_ENTRY;
1153 : :
1154 [ + - ]: 6 : g_assert (VALENT_IS_SMS_DEVICE (self));
1155 [ - + ]: 6 : g_assert (VALENT_IS_PACKET (packet));
1156 : :
1157 : 6 : body = valent_packet_get_body (packet);
1158 : 6 : node = json_object_get_member (body, "messages");
1159 [ + - - + ]: 6 : if (node == NULL || !JSON_NODE_HOLDS_ARRAY (node))
1160 : : {
1161 : 0 : g_warning ("%s(): expected \"messages\" field holding an array", G_STRFUNC);
1162 : 0 : return;
1163 : : }
1164 : :
1165 : : /* It's not clear if this could ever happen, or what it would imply if it did,
1166 : : * so log a debug message and bail.
1167 : : */
1168 : 6 : messages = json_node_get_array (node);
1169 : 6 : n_messages = json_array_get_length (messages);
1170 [ - + ]: 6 : if (n_messages == 0)
1171 : : {
1172 : 0 : g_debug ("%s(): expected \"messages\" field holding an array of objects",
1173 : : G_STRFUNC);
1174 : 0 : return;
1175 : : }
1176 : :
1177 : 6 : device = valent_extension_get_object (VALENT_EXTENSION (self));
1178 : 6 : thread_id = json_object_get_int_member (json_array_get_object_element (messages, 0),
1179 : : "thread_id");
1180 : 6 : thread_iri = g_strdup_printf ("urn:valent:messages:%s:%"PRId64,
1181 : : valent_device_get_id (device),
1182 : : thread_id);
1183 : :
1184 : : /* Check if there is an active request for this thread
1185 : : */
1186 : 6 : find_message_range (messages, &thread_id, &start_date, &end_date);
1187 [ + + ]: 6 : if (g_ptr_array_find_with_equal_func (self->message_requests,
1188 : : &thread_id,
1189 : : find_message_request,
1190 : : &index_))
1191 : : {
1192 : 3 : RequestData *request = g_ptr_array_index (self->message_requests, index_);
1193 : :
1194 : : /* This is a response to our request
1195 : : */
1196 [ + + ]: 3 : if (request->end_date == end_date)
1197 : : {
1198 [ - + ]: 1 : if (n_messages >= request->max_results &&
1199 [ # # ]: 0 : request->start_date < start_date)
1200 : : {
1201 : 0 : request->end_date = start_date;
1202 : 0 : valent_sms_device_request_conversation (self,
1203 : : request->thread_id,
1204 : : request->end_date,
1205 : : request->max_results);
1206 : : }
1207 : : else
1208 : : {
1209 : 1 : g_ptr_array_remove_index (self->message_requests, index_);
1210 : : }
1211 : : }
1212 : : }
1213 [ + - ]: 3 : else if (n_messages == 1)
1214 : : {
1215 : 3 : RequestData *request;
1216 : :
1217 : 3 : request = g_new (RequestData, 1);
1218 : 3 : request->thread_id = thread_id;
1219 : 3 : request->start_date = end_date;
1220 : 3 : request->end_date = end_date;
1221 : 3 : request->max_results = DEFAULT_MESSAGE_REQUEST;
1222 : :
1223 : 3 : valent_sms_device_get_timestamp (self,
1224 : : thread_id,
1225 : : self->cancellable,
1226 : : (GAsyncReadyCallback) valent_sms_device_get_timestamp_cb,
1227 : : g_steal_pointer (&request));
1228 : : }
1229 : :
1230 : : /* Store what we've received after the request is queued, otherwise having the
1231 : : * latest message we may request nothing.
1232 : : */
1233 : 6 : valent_sms_device_add_json (self, node);
1234 : :
1235 : 12 : VALENT_EXIT;
1236 : : }
1237 : :
1238 : : /**
1239 : : * valent_sms_device_handle_attachment_file:
1240 : : * @self: a `ValentSmsDevice`
1241 : : * @packet: a `kdeconnect.sms.attachment_file` packet
1242 : : *
1243 : : * Handle an attachment file.
1244 : : */
1245 : : void
1246 : 1 : valent_sms_device_handle_attachment_file (ValentSmsDevice *self,
1247 : : JsonNode *packet)
1248 : : {
1249 : 1 : ValentContext *context = NULL;
1250 : 1 : g_autoptr (ValentTransfer) transfer = NULL;
1251 [ + - ]: 1 : g_autoptr (GFile) file = NULL;
1252 : 1 : const char *filename = NULL;
1253 : :
1254 [ + - ]: 1 : g_assert (VALENT_IS_SMS_DEVICE (self));
1255 [ - + ]: 1 : g_assert (VALENT_IS_PACKET (packet));
1256 : :
1257 [ - + ]: 1 : if (!valent_packet_get_string (packet, "filename", &filename))
1258 : : {
1259 : 0 : g_warning ("%s(): expected \"filename\" field holding a string",
1260 : : G_STRFUNC);
1261 : 0 : return;
1262 : : }
1263 : :
1264 : 1 : context = valent_extension_get_context (VALENT_EXTENSION (self));
1265 : 1 : file = valent_context_get_cache_file (context, filename);
1266 : 1 : transfer = valent_device_transfer_new (self->device, packet, file);
1267 [ + - ]: 1 : valent_transfer_execute (transfer,
1268 : : self->cancellable,
1269 : : (GAsyncReadyCallback) handle_attachment_file_cb,
1270 : : self);
1271 : : }
1272 : :
|