LCOV - code coverage report
Current view: top level - src/plugins/sms - valent-sms-device.c (source / functions) Coverage Total Hit
Test: Code Coverage Lines: 0.0 % 601 0
Test Date: 2024-09-15 00:56:51 Functions: 0.0 % 36 0
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: - 0 0

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

Generated by: LCOV version 2.0-1