LCOV - code coverage report
Current view: top level - src/plugins/sms - valent-sms-device.c (source / functions) Coverage Total Hit
Test: Code Coverage Lines: 69.4 % 612 425
Test Date: 2024-12-21 23:29:11 Functions: 88.6 % 35 31
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 40.1 % 392 157

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

Generated by: LCOV version 2.0-1