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