LCOV - code coverage report
Current view: top level - src/plugins/gnome - valent-ui-utils.c (source / functions) Coverage Total Hit
Test: Code Coverage Lines: 11.2 % 402 45
Test Date: 2024-12-02 22:49:30 Functions: 20.8 % 24 5
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 5.5 % 344 19

             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-ui-utils"
       5                 :             : 
       6                 :             : #include "config.h"
       7                 :             : 
       8                 :             : #include <gio/gio.h>
       9                 :             : #include <libebook-contacts/libebook-contacts.h>
      10                 :             : #include <libtracker-sparql/tracker-sparql.h>
      11                 :             : 
      12                 :             : #include "valent-ui-utils-private.h"
      13                 :             : 
      14                 :             : /*< private>
      15                 :             :  *
      16                 :             :  * Cursor columns for `nco:Contact`.
      17                 :             :  */
      18                 :             : #define CURSOR_CONTACT_IRI                0
      19                 :             : #define CURSOR_CONTACT_UID                1
      20                 :             : #define CURSOR_VCARD_DATA                 2
      21                 :             : 
      22                 :             : #define SEARCH_CONTACTS_RQ "/ca/andyholmes/Valent/sparql/search-contacts.rq"
      23                 :             : 
      24                 :             : /*< private>
      25                 :             :  *
      26                 :             :  * Cursor columns for `vmo:PhoneMessage`.
      27                 :             :  */
      28                 :             : #define CURSOR_MESSAGE_IRI                0
      29                 :             : #define CURSOR_MESSAGE_BOX                1
      30                 :             : #define CURSOR_MESSAGE_DATE               2
      31                 :             : #define CURSOR_MESSAGE_ID                 3
      32                 :             : #define CURSOR_MESSAGE_READ               4
      33                 :             : #define CURSOR_MESSAGE_RECIPIENTS         5
      34                 :             : #define CURSOR_MESSAGE_SENDER             6
      35                 :             : #define CURSOR_MESSAGE_SUBSCRIPTION_ID    7
      36                 :             : #define CURSOR_MESSAGE_TEXT               8
      37                 :             : #define CURSOR_MESSAGE_THREAD_ID          9
      38                 :             : #define CURSOR_MESSAGE_ATTACHMENT_IRI     10
      39                 :             : #define CURSOR_MESSAGE_ATTACHMENT_PREVIEW 11
      40                 :             : #define CURSOR_MESSAGE_ATTACHMENT_FILE    12
      41                 :             : 
      42                 :             : #define SEARCH_MESSAGES_RQ "/ca/andyholmes/Valent/sparql/search-messages.rq"
      43                 :             : 
      44         [ +  + ]:           7 : G_DEFINE_QUARK (VALENT_CONTACT_PAINTABLE, valent_contact_paintable)
      45                 :             : 
      46                 :             : 
      47                 :             : // https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
      48                 :             : #define EMAIL_PATTERN "[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*"
      49                 :             : // https://mathiasbynens.be/demo/url-regex, @stephenhay, relaxed scheme
      50                 :             : #define URI_PATTERN   "\\b([a-zA-Z0-9-]+:[\\/]{1,3}|www[.])[^\\s>]*"
      51                 :             : #define COMPILE_FLAGS (G_REGEX_CASELESS | G_REGEX_MULTILINE | G_REGEX_NO_AUTO_CAPTURE | G_REGEX_OPTIMIZE)
      52                 :             : 
      53                 :             : static GRegex *email_regex = NULL;
      54                 :             : static GRegex *uri_regex = NULL;
      55                 :             : 
      56                 :             : static inline gpointer
      57                 :           0 : _g_object_dup0 (gpointer object,
      58                 :             :                 gpointer user_data)
      59                 :             : {
      60         [ #  # ]:           0 :   return object ? g_object_ref ((GObject *)object) : NULL;
      61                 :             : }
      62                 :             : 
      63                 :             : static gboolean
      64                 :           1 : valent_ui_replace_eval_uri (const GMatchInfo *info,
      65                 :             :                             GString          *result,
      66                 :             :                             gpointer          user_data)
      67                 :             : {
      68                 :           2 :   g_autofree char *uri = NULL;
      69                 :             : 
      70                 :           1 :   uri = g_match_info_fetch (info, 0);
      71                 :             : 
      72         [ +  - ]:           1 :   if (g_uri_is_valid (uri, G_URI_FLAGS_NONE, NULL))
      73                 :           1 :     g_string_append_printf (result, "<a href=\"%s\">%s</a>", uri, uri);
      74         [ #  # ]:           0 :   else if (g_regex_match (email_regex, uri, 0, NULL))
      75                 :           0 :     g_string_append_printf (result, "<a href=\"mailto:%s\">%s</a>", uri, uri);
      76                 :             :   else
      77                 :           0 :     g_string_append_printf (result, "<a href=\"https://%s\">%s</a>", uri, uri);
      78                 :             : 
      79                 :           1 :   return FALSE;
      80                 :             : }
      81                 :             : 
      82                 :             : /**
      83                 :             :  * valent_string_to_markup:
      84                 :             :  * @text: (nullable): input text
      85                 :             :  *
      86                 :             :  * Add markup to text for recognized elements.
      87                 :             :  *
      88                 :             :  * This function currently scans for URLs and e-mail addresses, then amends each
      89                 :             :  * element with anchor tags (`<a>`).
      90                 :             :  *
      91                 :             :  * If @text is %NULL, this function will return %NULL.
      92                 :             :  *
      93                 :             :  * Returns: (transfer full) (nullable): a string of markup
      94                 :             :  *
      95                 :             :  * Since: 1.0
      96                 :             :  */
      97                 :             : char *
      98                 :           1 : valent_string_to_markup (const char *text)
      99                 :             : {
     100                 :           2 :   g_autofree char *escaped = NULL;
     101                 :           1 :   g_autofree char *markup = NULL;
     102                 :           1 :   g_autoptr (GError) error = NULL;
     103                 :             : 
     104         [ +  - ]:           1 :   if G_UNLIKELY (text == NULL)
     105                 :             :     return NULL;
     106                 :             : 
     107         [ +  - ]:           1 :   if G_UNLIKELY (uri_regex == NULL)
     108                 :             :     {
     109                 :           1 :       email_regex = g_regex_new (EMAIL_PATTERN, COMPILE_FLAGS, 0, NULL);
     110                 :           1 :       uri_regex = g_regex_new (URI_PATTERN"|"EMAIL_PATTERN, COMPILE_FLAGS, 0, NULL);
     111                 :             :     }
     112                 :             : 
     113                 :           1 :   escaped = g_markup_escape_text (text, -1);
     114                 :           1 :   markup = g_regex_replace_eval (uri_regex,
     115                 :             :                                  escaped,
     116                 :           1 :                                  strlen (escaped),
     117                 :             :                                  0,
     118                 :             :                                  0,
     119                 :             :                                  valent_ui_replace_eval_uri,
     120                 :             :                                  NULL,
     121                 :             :                                  &error);
     122                 :             : 
     123                 :           1 :   return g_steal_pointer (&markup);
     124                 :             : }
     125                 :             : 
     126                 :             : static GdkPaintable *
     127                 :           4 : _e_contact_get_paintable (EContact  *contact,
     128                 :             :                           GError   **error)
     129                 :             : {
     130                 :           8 :   g_autoptr (EContactPhoto) photo = NULL;
     131                 :           4 :   GdkPaintable *paintable = NULL;
     132                 :           4 :   GdkTexture *texture = NULL;
     133                 :           4 :   const unsigned char *data;
     134                 :           4 :   size_t len;
     135                 :           4 :   const char *uri;
     136                 :             : 
     137   [ +  -  +  -  :           4 :   g_assert (E_IS_CONTACT (contact));
             -  +  -  - ]
     138                 :             : 
     139                 :           4 :   paintable = g_object_get_qdata (G_OBJECT (contact),
     140                 :             :                                   valent_contact_paintable_quark ());
     141                 :             : 
     142         [ +  - ]:           4 :   if (GDK_IS_PAINTABLE (paintable))
     143                 :             :     return paintable;
     144                 :             : 
     145                 :           4 :   photo = e_contact_get (contact, E_CONTACT_PHOTO);
     146         [ +  + ]:           4 :   if (photo == NULL)
     147                 :             :     return NULL;
     148                 :             : 
     149         [ +  - ]:           3 :   if (photo->type == E_CONTACT_PHOTO_TYPE_INLINED &&
     150         [ +  - ]:           3 :       (data = e_contact_photo_get_inlined (photo, &len)))
     151                 :             :     {
     152                 :           3 :       g_autoptr (GBytes) bytes = NULL;
     153                 :             : 
     154                 :           3 :       bytes = g_bytes_new (data, len);
     155         [ +  - ]:           3 :       texture = gdk_texture_new_from_bytes (bytes, NULL);
     156                 :             :     }
     157         [ #  # ]:           0 :   else if (photo->type == E_CONTACT_PHOTO_TYPE_URI &&
     158         [ #  # ]:           0 :            (uri = e_contact_photo_get_uri (photo)))
     159                 :             :     {
     160                 :           3 :       g_autoptr (GFile) file = NULL;
     161                 :             : 
     162                 :           0 :       file = g_file_new_for_uri (uri);
     163         [ #  # ]:           0 :       texture = gdk_texture_new_from_file (file, NULL);
     164                 :             :     }
     165                 :             : 
     166         [ +  - ]:           3 :   if (GDK_IS_PAINTABLE (texture))
     167                 :             :     {
     168                 :           3 :       g_object_set_qdata_full (G_OBJECT (contact),
     169                 :             :                                valent_contact_paintable_quark (),
     170                 :             :                                texture, /* owned */
     171                 :             :                                g_object_unref);
     172                 :             :     }
     173                 :             : 
     174                 :           3 :   return GDK_PAINTABLE (texture);
     175                 :             : }
     176                 :             : 
     177                 :             : GdkPaintable *
     178                 :           8 : valent_contact_to_paintable (gpointer  user_data,
     179                 :             :                              EContact *contact)
     180                 :             : {
     181                 :           8 :   GdkPaintable *paintable = NULL;
     182                 :             : 
     183         [ +  + ]:           8 :   if (contact != NULL)
     184                 :           4 :     paintable = _e_contact_get_paintable (contact, NULL);
     185                 :             : 
     186         [ +  + ]:           4 :   return paintable ? g_object_ref (paintable) : NULL;
     187                 :             : }
     188                 :             : 
     189                 :             : static EContact *
     190                 :           0 : _e_contact_from_sparql_cursor (TrackerSparqlCursor *cursor)
     191                 :             : {
     192                 :           0 :   const char *uid = NULL;
     193                 :           0 :   const char *vcard = NULL;
     194                 :             : 
     195         [ #  # ]:           0 :   g_assert (TRACKER_IS_SPARQL_CURSOR (cursor));
     196                 :             : 
     197   [ #  #  #  # ]:           0 :   if (!tracker_sparql_cursor_is_bound (cursor, CURSOR_CONTACT_UID) ||
     198                 :           0 :       !tracker_sparql_cursor_is_bound (cursor, CURSOR_VCARD_DATA))
     199                 :           0 :     g_return_val_if_reached (NULL);
     200                 :             : 
     201                 :           0 :   uid = tracker_sparql_cursor_get_string (cursor, CURSOR_CONTACT_UID, NULL);
     202                 :           0 :   vcard = tracker_sparql_cursor_get_string (cursor, CURSOR_VCARD_DATA, NULL);
     203                 :             : 
     204                 :           0 :   return e_contact_new_from_vcard_with_uid (vcard, uid);
     205                 :             : }
     206                 :             : 
     207                 :             : static void
     208                 :           0 : cursor_lookup_medium_cb (TrackerSparqlCursor *cursor,
     209                 :             :                          GAsyncResult        *result,
     210                 :             :                          gpointer             user_data)
     211                 :             : {
     212                 :           0 :   g_autoptr (GTask) task = G_TASK (g_steal_pointer (&user_data));
     213                 :           0 :   const char *medium = g_task_get_task_data (task);
     214                 :           0 :   EContact *contact = NULL;
     215         [ #  # ]:           0 :   g_autoptr (GError) error = NULL;
     216                 :             : 
     217         [ #  # ]:           0 :   if (tracker_sparql_cursor_next_finish (cursor, result, &error))
     218                 :           0 :     contact = _e_contact_from_sparql_cursor (cursor);
     219         [ #  # ]:           0 :   else if (error != NULL)
     220                 :           0 :     g_debug ("%s(): %s", G_STRFUNC, error->message);
     221                 :             : 
     222         [ #  # ]:           0 :   if (contact == NULL)
     223                 :             :     {
     224                 :           0 :       g_autoptr (EPhoneNumber) number = NULL;
     225                 :             : 
     226                 :           0 :       contact = e_contact_new ();
     227                 :           0 :       number = e_phone_number_from_string (medium, NULL, NULL);
     228         [ #  # ]:           0 :       if (number != NULL)
     229                 :             :         {
     230                 :           0 :           g_autofree char *name = NULL;
     231                 :             : 
     232                 :           0 :           name = e_phone_number_to_string (number,
     233                 :             :                                            E_PHONE_NUMBER_FORMAT_NATIONAL);
     234                 :           0 :           e_contact_set (contact, E_CONTACT_FULL_NAME, name);
     235                 :           0 :           e_contact_set (contact, E_CONTACT_PHONE_OTHER, medium);
     236                 :             :         }
     237                 :             :       else
     238                 :             :         {
     239                 :           0 :           e_contact_set (contact, E_CONTACT_FULL_NAME, medium);
     240         [ #  # ]:           0 :           if (g_strrstr (medium, "@") != NULL)
     241                 :           0 :             e_contact_set (contact, E_CONTACT_EMAIL_1, medium);
     242                 :             :           else
     243                 :           0 :             e_contact_set (contact, E_CONTACT_PHONE_OTHER, medium);
     244                 :             :         }
     245                 :             :     }
     246                 :             : 
     247                 :           0 :   g_task_return_pointer (task, g_steal_pointer (&contact), g_object_unref);
     248         [ #  # ]:           0 :   tracker_sparql_cursor_close (cursor);
     249                 :           0 : }
     250                 :             : 
     251                 :             : static void
     252                 :           0 : execute_lookup_medium_cb (TrackerSparqlStatement *stmt,
     253                 :             :                           GAsyncResult           *result,
     254                 :             :                           gpointer                user_data)
     255                 :             : {
     256                 :           0 :   g_autoptr (GTask) task = G_TASK (g_steal_pointer (&user_data));
     257   [ #  #  #  # ]:           0 :   g_autoptr (TrackerSparqlCursor) cursor = NULL;
     258                 :           0 :   GCancellable *cancellable = NULL;
     259                 :           0 :   GError *error = NULL;
     260                 :             : 
     261                 :           0 :   cursor = tracker_sparql_statement_execute_finish (stmt, result, &error);
     262         [ #  # ]:           0 :   if (cursor == NULL)
     263                 :             :     {
     264                 :           0 :       g_task_return_error (task, g_steal_pointer (&error));
     265         [ #  # ]:           0 :       return;
     266                 :             :     }
     267                 :             : 
     268                 :           0 :   cancellable = g_task_get_cancellable (G_TASK (result));
     269                 :           0 :   tracker_sparql_cursor_next_async (cursor,
     270                 :             :                                     cancellable,
     271                 :             :                                     (GAsyncReadyCallback) cursor_lookup_medium_cb,
     272                 :             :                                     g_object_ref (task));
     273                 :             : }
     274                 :             : 
     275                 :             : #define LOOKUP_MEDIUM_FMT                          \
     276                 :             : "SELECT ?contact ?uid ?vcardData                   \
     277                 :             : WHERE {                                            \
     278                 :             :   BIND(IRI(xsd:string(~medium)) AS ?contactMedium) \
     279                 :             :   ?contact nco:hasContactMedium ?contactMedium ;   \
     280                 :             :            nco:contactUID ?uid ;                   \
     281                 :             :            nie:plainTextContent ?vcardData .       \
     282                 :             : }                                                  \
     283                 :             : LIMIT 1"
     284                 :             : 
     285                 :             : /**
     286                 :             :  * valent_contacts_adapter_reverse_lookup:
     287                 :             :  * @store: a `ValentContactsAdapter`
     288                 :             :  * @medium: a contact medium
     289                 :             :  * @cancellable: (nullable): `GCancellable`
     290                 :             :  * @callback: (scope async): a `GAsyncReadyCallback`
     291                 :             :  * @user_data: user supplied data
     292                 :             :  *
     293                 :             :  * A convenience wrapper for finding a contact by phone number or email address.
     294                 :             :  *
     295                 :             :  * Call [method@Valent.ContactsAdapter.reverse_lookup_finish] to get the result.
     296                 :             :  */
     297                 :             : void
     298                 :           0 : valent_contacts_adapter_reverse_lookup (ValentContactsAdapter *adapter,
     299                 :             :                                         const char            *medium,
     300                 :             :                                         GCancellable          *cancellable,
     301                 :             :                                         GAsyncReadyCallback    callback,
     302                 :             :                                         gpointer               user_data)
     303                 :             : {
     304                 :           0 :   g_autoptr (TrackerSparqlConnection) connection = NULL;
     305         [ #  # ]:           0 :   g_autoptr (TrackerSparqlStatement) stmt = NULL;
     306         [ #  # ]:           0 :   g_autoptr (GTask) task = NULL;
     307                 :           0 :   GError *error = NULL;
     308         [ #  # ]:           0 :   g_autofree char *medium_iri = NULL;
     309                 :             : 
     310                 :           0 :   VALENT_ENTRY;
     311                 :             : 
     312         [ #  # ]:           0 :   g_return_if_fail (VALENT_IS_CONTACTS_ADAPTER (adapter));
     313   [ #  #  #  # ]:           0 :   g_return_if_fail (medium != NULL && *medium != '\0');
     314   [ #  #  #  #  :           0 :   g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
             #  #  #  # ]
     315                 :             : 
     316                 :           0 :   task = g_task_new (adapter, cancellable, callback, user_data);
     317         [ #  # ]:           0 :   g_task_set_source_tag (task, valent_contacts_adapter_reverse_lookup);
     318         [ #  # ]:           0 :   g_task_set_task_data (task, g_strdup (medium), g_free);
     319                 :             : 
     320         [ #  # ]:           0 :   if (g_strrstr (medium, "@") != NULL)
     321                 :             :     {
     322                 :           0 :       medium_iri = g_strdup_printf ("mailto:%s", medium);
     323                 :             :     }
     324                 :             :   else
     325                 :             :     {
     326                 :           0 :       g_autoptr (EPhoneNumber) number = NULL;
     327                 :             : 
     328                 :           0 :       number = e_phone_number_from_string (medium, NULL, NULL);
     329         [ #  # ]:           0 :       if (number != NULL)
     330                 :           0 :         medium_iri = e_phone_number_to_string (number, E_PHONE_NUMBER_FORMAT_RFC3966);
     331                 :             :       else
     332                 :           0 :         medium_iri = g_strdup_printf ("tel:%s", medium);
     333                 :             :     }
     334                 :             : 
     335                 :           0 :   g_object_get (adapter, "connection", &connection, NULL);
     336                 :           0 :   stmt = tracker_sparql_connection_query_statement (connection,
     337                 :             :                                                     LOOKUP_MEDIUM_FMT,
     338                 :             :                                                     cancellable,
     339                 :             :                                                     &error);
     340                 :             : 
     341         [ #  # ]:           0 :   if (stmt == NULL)
     342                 :             :     {
     343                 :           0 :       g_task_return_error (task, g_steal_pointer (&error));
     344                 :           0 :       VALENT_EXIT;
     345                 :             :     }
     346                 :             : 
     347                 :           0 :   tracker_sparql_statement_bind_string (stmt, "medium", medium_iri);
     348                 :           0 :   tracker_sparql_statement_execute_async (stmt,
     349                 :             :                                           cancellable,
     350                 :             :                                           (GAsyncReadyCallback) execute_lookup_medium_cb,
     351                 :             :                                           g_object_ref (task));
     352                 :             : 
     353                 :           0 :   VALENT_EXIT;
     354                 :             : }
     355                 :             : 
     356                 :             : /**
     357                 :             :  * valent_contacts_adapter_reverse_lookup_finish:
     358                 :             :  * @adapter: a `ValentContactsAdapter`
     359                 :             :  * @result: a `GAsyncResult`
     360                 :             :  * @error: (nullable): a `GError`
     361                 :             :  *
     362                 :             :  * Finish an operation started by [method@Valent.ContactsAdapter.reverse_lookup].
     363                 :             :  *
     364                 :             :  * Returns: (transfer full): an `EContact`
     365                 :             :  */
     366                 :             : EContact *
     367                 :           0 : valent_contacts_adapter_reverse_lookup_finish (ValentContactsAdapter  *adapter,
     368                 :             :                                                GAsyncResult           *result,
     369                 :             :                                                GError                **error)
     370                 :             : {
     371         [ #  # ]:           0 :   g_return_val_if_fail (VALENT_IS_CONTACTS_ADAPTER (adapter), NULL);
     372         [ #  # ]:           0 :   g_return_val_if_fail (g_task_is_valid (result, adapter), NULL);
     373   [ #  #  #  # ]:           0 :   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
     374                 :             : 
     375                 :           0 :   return g_task_propagate_pointer (G_TASK (result), error);
     376                 :             : }
     377                 :             : 
     378                 :             : static void
     379                 :           0 : cursor_search_contacts_cb (TrackerSparqlCursor *cursor,
     380                 :             :                            GAsyncResult        *result,
     381                 :             :                            gpointer             user_data)
     382                 :             : {
     383                 :           0 :   g_autoptr (GTask) task = G_TASK (g_steal_pointer (&user_data));
     384                 :           0 :   GCancellable *cancellable = g_task_get_cancellable (task);
     385                 :           0 :   GListStore *contacts = g_task_get_task_data (task);
     386   [ #  #  #  # ]:           0 :   g_autoptr (GError) error = NULL;
     387                 :             : 
     388         [ #  # ]:           0 :   if (tracker_sparql_cursor_next_finish (cursor, result, &error))
     389                 :             :     {
     390         [ #  # ]:           0 :       g_autoptr (EContact) contact = NULL;
     391                 :             : 
     392                 :           0 :       contact = _e_contact_from_sparql_cursor (cursor);
     393         [ #  # ]:           0 :       if (contact != NULL)
     394                 :           0 :         g_list_store_append (contacts, contact);
     395                 :             : 
     396                 :           0 :       tracker_sparql_cursor_next_async (cursor,
     397                 :             :                                         cancellable,
     398                 :             :                                         (GAsyncReadyCallback) cursor_search_contacts_cb,
     399                 :             :                                         g_object_ref (task));
     400         [ #  # ]:           0 :       return;
     401                 :             :     }
     402                 :             : 
     403         [ #  # ]:           0 :   if (error != NULL)
     404                 :           0 :     g_task_return_error (task, g_steal_pointer (&error));
     405                 :             :   else
     406                 :           0 :     g_task_return_pointer (task, g_object_ref (contacts), g_object_unref);
     407                 :             : 
     408         [ #  # ]:           0 :   tracker_sparql_cursor_close (cursor);
     409                 :             : }
     410                 :             : 
     411                 :             : static void
     412                 :           0 : execute_search_contacts_cb (TrackerSparqlStatement *stmt,
     413                 :             :                             GAsyncResult           *result,
     414                 :             :                             gpointer                user_data)
     415                 :             : {
     416                 :           0 :   g_autoptr (GTask) task = G_TASK (g_steal_pointer (&user_data));
     417                 :           0 :   GCancellable *cancellable = g_task_get_cancellable (task);
     418   [ #  #  #  # ]:           0 :   g_autoptr (TrackerSparqlCursor) cursor = NULL;
     419                 :           0 :   GError *error = NULL;
     420                 :             : 
     421                 :           0 :   cursor = tracker_sparql_statement_execute_finish (stmt, result, &error);
     422         [ #  # ]:           0 :   if (cursor == NULL)
     423                 :             :     {
     424                 :           0 :       g_task_return_error (task, g_steal_pointer (&error));
     425         [ #  # ]:           0 :       return;
     426                 :             :     }
     427                 :             : 
     428                 :           0 :   tracker_sparql_cursor_next_async (cursor,
     429                 :             :                                     cancellable,
     430                 :             :                                     (GAsyncReadyCallback) cursor_search_contacts_cb,
     431                 :             :                                     g_object_ref (task));
     432                 :             : }
     433                 :             : 
     434                 :             : /**
     435                 :             :  * valent_contacts_adapter_search:
     436                 :             :  * @adapter: a `ValentContactsAdapter`
     437                 :             :  * @query: a string to search for
     438                 :             :  * @cancellable: (nullable): a `GCancellable`
     439                 :             :  * @callback: (scope async): a `GAsyncReadyCallback`
     440                 :             :  * @user_data: user supplied data
     441                 :             :  *
     442                 :             :  * Search through all the contacts in @adapter and return the most recent message
     443                 :             :  * from each thread containing @query.
     444                 :             :  *
     445                 :             :  * Call [method@Valent.ContactsAdapter.search_contacts_finish] to get the result.
     446                 :             :  *
     447                 :             :  * Since: 1.0
     448                 :             :  */
     449                 :             : void
     450                 :           0 : valent_contacts_adapter_search (ValentContactsAdapter *adapter,
     451                 :             :                                 const char            *query,
     452                 :             :                                 GCancellable          *cancellable,
     453                 :             :                                 GAsyncReadyCallback    callback,
     454                 :             :                                 gpointer               user_data)
     455                 :             : {
     456                 :           0 :   g_autoptr (TrackerSparqlStatement) stmt = NULL;
     457         [ #  # ]:           0 :   g_autoptr (GTask) task = NULL;
     458         [ #  # ]:           0 :   g_autofree char *query_sanitized = NULL;
     459                 :           0 :   GError *error = NULL;
     460                 :             : 
     461                 :           0 :   VALENT_ENTRY;
     462                 :             : 
     463         [ #  # ]:           0 :   g_return_if_fail (VALENT_IS_CONTACTS_ADAPTER (adapter));
     464         [ #  # ]:           0 :   g_return_if_fail (query != NULL);
     465   [ #  #  #  #  :           0 :   g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
             #  #  #  # ]
     466                 :             : 
     467                 :           0 :   task = g_task_new (adapter, cancellable, callback, user_data);
     468         [ #  # ]:           0 :   g_task_set_source_tag (task, valent_contacts_adapter_search);
     469                 :           0 :   g_task_set_task_data (task, g_list_store_new (E_TYPE_CONTACT), g_object_unref);
     470                 :             : 
     471                 :           0 :   stmt = g_object_dup_data (G_OBJECT (adapter),
     472                 :             :                             "valent-contacts-adapter-search",
     473                 :             :                             _g_object_dup0,
     474                 :             :                             NULL);
     475                 :             : 
     476         [ #  # ]:           0 :   if (stmt == NULL)
     477                 :             :     {
     478                 :           0 :       g_autoptr (TrackerSparqlConnection) connection = NULL;
     479                 :             : 
     480                 :           0 :       g_object_get (adapter, "connection", &connection, NULL);
     481                 :           0 :       stmt = tracker_sparql_connection_load_statement_from_gresource (connection,
     482                 :             :                                                                       SEARCH_CONTACTS_RQ,
     483                 :             :                                                                       cancellable,
     484                 :             :                                                                       &error);
     485                 :             : 
     486         [ #  # ]:           0 :       if (stmt == NULL)
     487                 :             :         {
     488                 :           0 :           g_task_return_error (task, g_steal_pointer (&error));
     489         [ #  # ]:           0 :           return;
     490                 :             :         }
     491                 :             : 
     492         [ #  # ]:           0 :       g_object_set_data_full (G_OBJECT (adapter),
     493                 :             :                               "valent-contacts-adapter-search",
     494                 :             :                               g_object_ref (stmt),
     495                 :             :                               g_object_unref);
     496                 :             :     }
     497                 :             : 
     498                 :           0 :   query_sanitized = tracker_sparql_escape_string (query);
     499                 :           0 :   tracker_sparql_statement_bind_string (stmt, "query", query_sanitized);
     500                 :           0 :   tracker_sparql_statement_execute_async (stmt,
     501                 :             :                                           cancellable,
     502                 :             :                                           (GAsyncReadyCallback) execute_search_contacts_cb,
     503                 :             :                                           g_object_ref (task));
     504                 :             : 
     505                 :           0 :   VALENT_EXIT;
     506                 :             : }
     507                 :             : 
     508                 :             : /**
     509                 :             :  * valent_contacts_adapter_search_finish:
     510                 :             :  * @adapter: a `ValentContactsAdapter`
     511                 :             :  * @result: a `GAsyncResult`
     512                 :             :  * @error: (nullable): a `GError`
     513                 :             :  *
     514                 :             :  * Finish an operation started by [method@Valent.ContactsAdapter.search].
     515                 :             :  *
     516                 :             :  * Returns: (transfer full) (element-type Valent.Message): a list of contacts
     517                 :             :  *
     518                 :             :  * Since: 1.0
     519                 :             :  */
     520                 :             : GListModel *
     521                 :           0 : valent_contacts_adapter_search_finish (ValentContactsAdapter  *adapter,
     522                 :             :                                        GAsyncResult           *result,
     523                 :             :                                        GError                **error)
     524                 :             : {
     525                 :           0 :   GListModel *ret;
     526                 :             : 
     527                 :           0 :   VALENT_ENTRY;
     528                 :             : 
     529         [ #  # ]:           0 :   g_return_val_if_fail (VALENT_IS_CONTACTS_ADAPTER (adapter), NULL);
     530         [ #  # ]:           0 :   g_return_val_if_fail (g_task_is_valid (result, adapter), NULL);
     531   [ #  #  #  # ]:           0 :   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
     532                 :             : 
     533                 :           0 :   ret = g_task_propagate_pointer (G_TASK (result), error);
     534                 :             : 
     535                 :           0 :   VALENT_RETURN (ret);
     536                 :             : }
     537                 :             : 
     538                 :             : static void
     539                 :           0 : cursor_lookup_thread_cb (TrackerSparqlCursor *cursor,
     540                 :             :                          GAsyncResult        *result,
     541                 :             :                          gpointer             user_data)
     542                 :             : {
     543                 :           0 :   g_autoptr (GTask) task = G_TASK (g_steal_pointer (&user_data));
     544         [ #  # ]:           0 :   g_autoptr (GError) error = NULL;
     545                 :             : 
     546   [ #  #  #  # ]:           0 :   if (tracker_sparql_cursor_next_finish (cursor, result, &error) &&
     547                 :           0 :       tracker_sparql_cursor_is_bound (cursor, 0))
     548                 :           0 :     {
     549                 :           0 :       const char *iri = NULL;
     550                 :             : 
     551                 :           0 :       iri = tracker_sparql_cursor_get_string (cursor, 0, NULL);
     552         [ #  # ]:           0 :       g_task_return_pointer (task, g_strdup (iri), g_free);
     553                 :             :     }
     554                 :             :   else
     555                 :             :     {
     556         [ #  # ]:           0 :       if (error == NULL)
     557                 :             :         {
     558                 :           0 :           g_set_error_literal (&error,
     559                 :             :                                G_IO_ERROR,
     560                 :             :                                G_IO_ERROR_NOT_FOUND,
     561                 :             :                                "Failed to find thread");
     562                 :             :         }
     563                 :             : 
     564                 :           0 :       g_task_return_error (task, g_steal_pointer (&error));
     565                 :             :     }
     566                 :             : 
     567         [ #  # ]:           0 :   tracker_sparql_cursor_close (cursor);
     568                 :           0 : }
     569                 :             : 
     570                 :             : static void
     571                 :           0 : execute_lookup_thread_cb (TrackerSparqlStatement *stmt,
     572                 :             :                           GAsyncResult           *result,
     573                 :             :                           gpointer                user_data)
     574                 :             : {
     575                 :           0 :   g_autoptr (GTask) task = G_TASK (g_steal_pointer (&user_data));
     576   [ #  #  #  # ]:           0 :   g_autoptr (TrackerSparqlCursor) cursor = NULL;
     577                 :           0 :   GCancellable *cancellable = NULL;
     578                 :           0 :   GError *error = NULL;
     579                 :             : 
     580                 :           0 :   cursor = tracker_sparql_statement_execute_finish (stmt, result, &error);
     581         [ #  # ]:           0 :   if (cursor == NULL)
     582                 :             :     {
     583                 :           0 :       g_task_return_error (task, g_steal_pointer (&error));
     584         [ #  # ]:           0 :       return;
     585                 :             :     }
     586                 :             : 
     587                 :           0 :   cancellable = g_task_get_cancellable (G_TASK (result));
     588                 :           0 :   tracker_sparql_cursor_next_async (cursor,
     589                 :             :                                     cancellable,
     590                 :             :                                     (GAsyncReadyCallback) cursor_lookup_thread_cb,
     591                 :             :                                     g_object_ref (task));
     592                 :             : }
     593                 :             : 
     594                 :             : #define LOOKUP_THREAD_FMT                                        \
     595                 :             : "SELECT DISTINCT ?communicationChannel                           \
     596                 :             : WHERE {                                                          \
     597                 :             :   VALUES ?specifiedIRIs { %s }                                   \
     598                 :             :   ?communicationChannel vmo:hasParticipant ?participant .        \
     599                 :             :   FILTER (?participant IN (%s))                                  \
     600                 :             :   FILTER NOT EXISTS {                                            \
     601                 :             :     ?communicationChannel vmo:hasParticipant ?otherParticipant . \
     602                 :             :     FILTER (?otherParticipant NOT IN (%s))                       \
     603                 :             :   }                                                              \
     604                 :             : }                                                                \
     605                 :             : GROUP BY ?communicationChannel                                   \
     606                 :             : HAVING (COUNT(DISTINCT ?participant) = %u)"
     607                 :             : 
     608                 :             : /**
     609                 :             :  * valent_messages_adapter_lookup_thread:
     610                 :             :  * @adapter: a `ValentMessagesAdapter`
     611                 :             :  * @participants: a list of contact mediums
     612                 :             :  * @cancellable: (nullable): a `GCancellable`
     613                 :             :  * @callback: (scope async): a `GAsyncReadyCallback`
     614                 :             :  * @user_data: user supplied data
     615                 :             :  *
     616                 :             :  * Find the thread with @participants.
     617                 :             :  *
     618                 :             :  * Since: 1.0
     619                 :             :  */
     620                 :             : void
     621                 :           0 : valent_messages_adapter_lookup_thread (ValentMessagesAdapter *adapter,
     622                 :             :                                        const char * const    *participants,
     623                 :             :                                        GCancellable          *cancellable,
     624                 :             :                                        GAsyncReadyCallback    callback,
     625                 :             :                                        gpointer               user_data)
     626                 :             : {
     627                 :           0 :   g_autoptr (TrackerSparqlConnection) connection = NULL;
     628         [ #  # ]:           0 :   g_autoptr (TrackerSparqlStatement) stmt = NULL;
     629         [ #  # ]:           0 :   g_autoptr (GTask) task = NULL;
     630         [ #  # ]:           0 :   g_autoptr (GStrvBuilder) builder = NULL;
     631         [ #  # ]:           0 :   g_auto (GStrv) iriv = NULL;
     632         [ #  # ]:           0 :   g_autofree char *iris = NULL;
     633                 :           0 :   g_autofree char *values = NULL;
     634                 :           0 :   g_autofree char *sparql = NULL;
     635                 :           0 :   GError *error = NULL;
     636                 :             : 
     637                 :           0 :   VALENT_ENTRY;
     638                 :             : 
     639         [ #  # ]:           0 :   g_return_if_fail (VALENT_IS_MESSAGES_ADAPTER (adapter));
     640   [ #  #  #  # ]:           0 :   g_return_if_fail (participants != NULL && participants[0] != NULL);
     641   [ #  #  #  #  :           0 :   g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
             #  #  #  # ]
     642                 :             : 
     643                 :           0 :   task = g_task_new (adapter, cancellable, callback, user_data);
     644         [ #  # ]:           0 :   g_task_set_source_tag (task, valent_messages_adapter_lookup_thread);
     645                 :           0 :   g_task_set_task_data (task,
     646                 :           0 :                         g_ptr_array_new_with_free_func (g_object_unref),
     647                 :             :                         (GDestroyNotify)g_ptr_array_unref);
     648                 :             : 
     649                 :           0 :   builder = g_strv_builder_new ();
     650         [ #  # ]:           0 :   for (size_t i = 0; participants[i] != NULL; i++)
     651                 :             :     {
     652                 :           0 :       g_autofree char *iri = NULL;
     653                 :             : 
     654         [ #  # ]:           0 :       if (g_strrstr (participants[i], "@"))
     655                 :             :         {
     656                 :           0 :           iri = g_strdup_printf ("<mailto:%s>", participants[i]);
     657                 :             :         }
     658                 :             :       else
     659                 :             :         {
     660                 :           0 :           g_autoptr (EPhoneNumber) number = NULL;
     661                 :             : 
     662                 :           0 :           number = e_phone_number_from_string (participants[i], NULL, NULL);
     663         [ #  # ]:           0 :           if (number != NULL)
     664                 :             :             {
     665                 :           0 :               g_autofree char *uri = NULL;
     666                 :             : 
     667                 :           0 :               uri = e_phone_number_to_string (number, E_PHONE_NUMBER_FORMAT_RFC3966);
     668                 :           0 :               iri = g_strdup_printf ("<%s>", uri);
     669                 :             :             }
     670                 :             :         }
     671                 :             : 
     672         [ #  # ]:           0 :       if (iri != NULL)
     673                 :           0 :         g_strv_builder_take (builder, g_steal_pointer (&iri));
     674                 :             :     }
     675                 :           0 :   iriv = g_strv_builder_end (builder);
     676                 :             : 
     677                 :           0 :   iris = g_strjoinv (", ", iriv);
     678                 :           0 :   values = g_strjoinv (" ", iriv);
     679                 :           0 :   sparql = g_strdup_printf (LOOKUP_THREAD_FMT,
     680                 :             :                             values,
     681                 :             :                             iris,
     682                 :             :                             iris,
     683                 :             :                             g_strv_length ((GStrv)iriv));
     684                 :             : 
     685                 :           0 :   g_object_get (adapter, "connection", &connection, NULL);
     686                 :           0 :   stmt = tracker_sparql_connection_query_statement (connection,
     687                 :             :                                                     sparql,
     688                 :             :                                                     cancellable,
     689                 :             :                                                     &error);
     690                 :             : 
     691         [ #  # ]:           0 :   if (stmt == NULL)
     692                 :             :     {
     693                 :           0 :       g_task_return_error (task, g_steal_pointer (&error));
     694                 :           0 :       VALENT_EXIT;
     695                 :             :     }
     696                 :             : 
     697                 :           0 :   tracker_sparql_statement_execute_async (stmt,
     698                 :             :                                           cancellable,
     699                 :             :                                           (GAsyncReadyCallback) execute_lookup_thread_cb,
     700                 :             :                                           g_object_ref (task));
     701                 :             : 
     702                 :           0 :   VALENT_EXIT;
     703                 :             : }
     704                 :             : 
     705                 :             : /**
     706                 :             :  * valent_messages_adapter_lookup_thread_finish:
     707                 :             :  * @adapter: a `ValentMessagesAdapter`
     708                 :             :  * @result: a `GAsyncResult`
     709                 :             :  * @error: (nullable): a `GError`
     710                 :             :  *
     711                 :             :  * Finish an operation started by valent_contact_adapter_lookup_contact().
     712                 :             :  *
     713                 :             :  * Returns: (transfer full): an `EContact`
     714                 :             :  */
     715                 :             : GListModel *
     716                 :           0 : valent_messages_adapter_lookup_thread_finish (ValentMessagesAdapter  *adapter,
     717                 :             :                                               GAsyncResult           *result,
     718                 :             :                                               GError                **error)
     719                 :             : {
     720                 :           0 :   GListModel *ret = NULL;
     721                 :           0 :   g_autofree char *iri = NULL;
     722                 :             : 
     723                 :           0 :   VALENT_ENTRY;
     724                 :             : 
     725         [ #  # ]:           0 :   g_return_val_if_fail (VALENT_IS_MESSAGES_ADAPTER (adapter), NULL);
     726         [ #  # ]:           0 :   g_return_val_if_fail (g_task_is_valid (result, adapter), NULL);
     727   [ #  #  #  # ]:           0 :   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
     728                 :             : 
     729                 :           0 :   iri = g_task_propagate_pointer (G_TASK (result), error);
     730         [ #  # ]:           0 :   if (iri != NULL)
     731                 :             :     {
     732                 :           0 :       unsigned int n_threads = g_list_model_get_n_items (G_LIST_MODEL (adapter));
     733                 :             : 
     734         [ #  # ]:           0 :       for (unsigned int i = 0; i < n_threads; i++)
     735                 :             :         {
     736                 :           0 :           g_autoptr (GListModel) thread = NULL;
     737         [ #  # ]:           0 :           g_autofree char *thread_iri = NULL;
     738                 :             : 
     739                 :           0 :           thread = g_list_model_get_item (G_LIST_MODEL (adapter), i);
     740                 :           0 :           g_object_get (thread, "iri", &thread_iri, NULL);
     741                 :             : 
     742         [ #  # ]:           0 :           if (g_strcmp0 (iri, thread_iri) == 0)
     743                 :             :             {
     744                 :           0 :               ret = g_steal_pointer (&thread);
     745                 :           0 :               break;
     746                 :             :             }
     747                 :             :         }
     748                 :             :     }
     749                 :             : 
     750                 :           0 :   VALENT_RETURN (ret);
     751                 :             : }
     752                 :             : 
     753                 :             : static ValentMessage *
     754                 :           0 : valent_message_from_sparql_cursor (TrackerSparqlCursor *cursor,
     755                 :             :                                    ValentMessage       *current)
     756                 :             : {
     757                 :           0 :   ValentMessage *ret = NULL;
     758                 :           0 :   int64_t message_id;
     759                 :             : 
     760         [ #  # ]:           0 :   g_assert (TRACKER_IS_SPARQL_CURSOR (cursor));
     761   [ #  #  #  # ]:           0 :   g_assert (current == NULL || VALENT_IS_MESSAGE (current));
     762                 :             : 
     763                 :           0 :   message_id = tracker_sparql_cursor_get_integer (cursor, CURSOR_MESSAGE_ID);
     764   [ #  #  #  # ]:           0 :   if (current != NULL && valent_message_get_id (current) == message_id)
     765                 :             :     {
     766                 :           0 :       ret = g_object_ref (current);
     767                 :             :     }
     768                 :             :   else
     769                 :             :     {
     770                 :           0 :       const char *iri = tracker_sparql_cursor_get_string (cursor, CURSOR_MESSAGE_IRI, NULL);
     771                 :           0 :       g_autoptr (GListStore) attachments = NULL;
     772                 :           0 :       ValentMessageBox box = VALENT_MESSAGE_BOX_ALL;
     773                 :           0 :       int64_t date = 0;
     774         [ #  # ]:           0 :       g_autoptr (GDateTime) datetime = NULL;
     775                 :           0 :       gboolean read = FALSE;
     776                 :           0 :       const char *recipients = NULL;
     777         [ #  # ]:           0 :       g_auto (GStrv) recipientv = NULL;
     778                 :           0 :       const char *sender = NULL;
     779                 :           0 :       int64_t subscription_id = -1;
     780                 :           0 :       const char *text = NULL;
     781                 :           0 :       int64_t thread_id = -1;
     782                 :             : 
     783                 :           0 :       attachments = g_list_store_new (VALENT_TYPE_MESSAGE_ATTACHMENT);
     784                 :           0 :       box = tracker_sparql_cursor_get_integer (cursor, CURSOR_MESSAGE_BOX);
     785                 :             : 
     786                 :           0 :       datetime = tracker_sparql_cursor_get_datetime (cursor, CURSOR_MESSAGE_DATE);
     787         [ #  # ]:           0 :       if (datetime != NULL)
     788                 :           0 :         date = g_date_time_to_unix_usec (datetime) / 1000;
     789                 :             : 
     790                 :           0 :       read = tracker_sparql_cursor_get_boolean (cursor, CURSOR_MESSAGE_READ);
     791                 :             : 
     792                 :           0 :       recipients = tracker_sparql_cursor_get_string (cursor, CURSOR_MESSAGE_RECIPIENTS, NULL);
     793         [ #  # ]:           0 :       if (recipients != NULL)
     794                 :           0 :         recipientv = g_strsplit (recipients, ",", -1);
     795                 :             : 
     796         [ #  # ]:           0 :       if (tracker_sparql_cursor_is_bound (cursor, CURSOR_MESSAGE_SENDER))
     797                 :           0 :         sender = tracker_sparql_cursor_get_string (cursor, CURSOR_MESSAGE_SENDER, NULL);
     798                 :             : 
     799         [ #  # ]:           0 :       if (tracker_sparql_cursor_is_bound (cursor, CURSOR_MESSAGE_SUBSCRIPTION_ID))
     800                 :           0 :         subscription_id = tracker_sparql_cursor_get_integer (cursor, CURSOR_MESSAGE_SUBSCRIPTION_ID);
     801                 :             : 
     802         [ #  # ]:           0 :       if (tracker_sparql_cursor_is_bound (cursor, CURSOR_MESSAGE_TEXT))
     803                 :           0 :         text = tracker_sparql_cursor_get_string (cursor, CURSOR_MESSAGE_TEXT, NULL);
     804                 :             : 
     805                 :           0 :       thread_id = tracker_sparql_cursor_get_integer (cursor, CURSOR_MESSAGE_THREAD_ID);
     806                 :             : 
     807         [ #  # ]:           0 :       ret = g_object_new (VALENT_TYPE_MESSAGE,
     808                 :             :                           "iri",             iri,
     809                 :             :                           "box",             box,
     810                 :             :                           "date",            date,
     811                 :             :                           "id",              message_id,
     812                 :             :                           "read",            read,
     813                 :             :                           "recipients",      recipientv,
     814                 :             :                           "sender",          sender,
     815                 :             :                           "subscription-id", subscription_id,
     816                 :             :                           "text",            text,
     817                 :             :                           "thread-id",       thread_id,
     818                 :             :                           "attachments",     attachments,
     819                 :             :                           NULL);
     820                 :             :     }
     821                 :             : 
     822                 :             :   /* Attachment
     823                 :             :    */
     824         [ #  # ]:           0 :   if (tracker_sparql_cursor_is_bound (cursor, CURSOR_MESSAGE_ATTACHMENT_IRI))
     825                 :             :     {
     826                 :           0 :       const char *iri = tracker_sparql_cursor_get_string (cursor, CURSOR_MESSAGE_ATTACHMENT_IRI, NULL);
     827                 :           0 :       GListModel *attachments = valent_message_get_attachments (ret);
     828                 :           0 :       g_autoptr (ValentMessageAttachment) attachment = NULL;
     829         [ #  # ]:           0 :       g_autoptr (GIcon) preview = NULL;
     830         [ #  # ]:           0 :       g_autoptr (GFile) file = NULL;
     831                 :             : 
     832         [ #  # ]:           0 :       if (tracker_sparql_cursor_is_bound (cursor, CURSOR_MESSAGE_ATTACHMENT_PREVIEW))
     833                 :             :         {
     834                 :           0 :           const char *base64_data;
     835                 :             : 
     836                 :           0 :           base64_data = tracker_sparql_cursor_get_string (cursor, CURSOR_MESSAGE_ATTACHMENT_PREVIEW, NULL);
     837         [ #  # ]:           0 :           if (base64_data != NULL)
     838                 :             :             {
     839                 :           0 :               g_autoptr (GBytes) bytes = NULL;
     840                 :           0 :               unsigned char *data;
     841                 :           0 :               size_t len;
     842                 :             : 
     843                 :           0 :               data = g_base64_decode (base64_data, &len);
     844                 :           0 :               bytes = g_bytes_new_take (g_steal_pointer (&data), len);
     845         [ #  # ]:           0 :               preview = g_bytes_icon_new (bytes);
     846                 :             :             }
     847                 :             :         }
     848                 :             : 
     849         [ #  # ]:           0 :       if (tracker_sparql_cursor_is_bound (cursor, CURSOR_MESSAGE_ATTACHMENT_FILE))
     850                 :             :         {
     851                 :           0 :           const char *file_uri;
     852                 :             : 
     853                 :           0 :           file_uri = tracker_sparql_cursor_get_string (cursor, CURSOR_MESSAGE_ATTACHMENT_FILE, NULL);
     854         [ #  # ]:           0 :           if (file_uri != NULL)
     855                 :           0 :             file = g_file_new_for_uri (file_uri);
     856                 :             :         }
     857                 :             : 
     858                 :           0 :       attachment = g_object_new (VALENT_TYPE_MESSAGE_ATTACHMENT,
     859                 :             :                                  "iri",     iri,
     860                 :             :                                  "preview", preview,
     861                 :             :                                  "file",    file,
     862                 :             :                                  NULL);
     863         [ #  # ]:           0 :       g_list_store_append (G_LIST_STORE (attachments), attachment);
     864                 :             :     }
     865                 :             : 
     866                 :           0 :   return g_steal_pointer (&ret);
     867                 :             : }
     868                 :             : 
     869                 :             : static void
     870                 :           0 : cursor_search_messages_cb (TrackerSparqlCursor *cursor,
     871                 :             :                            GAsyncResult        *result,
     872                 :             :                            gpointer             user_data)
     873                 :             : {
     874                 :           0 :   g_autoptr (GTask) task = G_TASK (g_steal_pointer (&user_data));
     875                 :           0 :   GListStore *messages = g_task_get_task_data (task);
     876   [ #  #  #  # ]:           0 :   g_autoptr (GError) error = NULL;
     877                 :             : 
     878         [ #  # ]:           0 :   if (tracker_sparql_cursor_next_finish (cursor, result, &error))
     879                 :             :     {
     880         [ #  # ]:           0 :       g_autoptr (ValentMessage) current = NULL;
     881         [ #  # ]:           0 :       g_autoptr (ValentMessage) message = NULL;
     882                 :           0 :       unsigned int n_items = 0;
     883                 :             : 
     884                 :           0 :       n_items = g_list_model_get_n_items (G_LIST_MODEL (messages));
     885         [ #  # ]:           0 :       if (n_items > 0)
     886                 :           0 :         current = g_list_model_get_item (G_LIST_MODEL (messages), n_items - 1);
     887                 :             : 
     888                 :           0 :       message = valent_message_from_sparql_cursor (cursor, current);
     889         [ #  # ]:           0 :       if (message != current)
     890                 :           0 :         g_list_store_append (messages, message);
     891                 :             : 
     892                 :           0 :       tracker_sparql_cursor_next_async (cursor,
     893                 :             :                                         g_task_get_cancellable (task),
     894                 :             :                                         (GAsyncReadyCallback) cursor_search_messages_cb,
     895                 :             :                                         g_object_ref (task));
     896         [ #  # ]:           0 :       return;
     897                 :             :     }
     898                 :             : 
     899         [ #  # ]:           0 :   if (error != NULL)
     900                 :           0 :     g_task_return_error (task, g_steal_pointer (&error));
     901                 :             :   else
     902                 :           0 :     g_task_return_pointer (task, g_object_ref (messages), g_object_unref);
     903                 :             : 
     904         [ #  # ]:           0 :   tracker_sparql_cursor_close (cursor);
     905                 :             : }
     906                 :             : 
     907                 :             : static void
     908                 :           0 : execute_search_messages_cb (TrackerSparqlStatement *stmt,
     909                 :             :                             GAsyncResult           *result,
     910                 :             :                             gpointer                user_data)
     911                 :             : {
     912                 :           0 :   g_autoptr (GTask) task = G_TASK (g_steal_pointer (&user_data));
     913                 :           0 :   GCancellable *cancellable = g_task_get_cancellable (task);
     914   [ #  #  #  # ]:           0 :   g_autoptr (TrackerSparqlCursor) cursor = NULL;
     915                 :           0 :   GError *error = NULL;
     916                 :             : 
     917                 :           0 :   cursor = tracker_sparql_statement_execute_finish (stmt, result, &error);
     918         [ #  # ]:           0 :   if (cursor == NULL)
     919                 :             :     {
     920                 :           0 :       g_task_return_error (task, g_steal_pointer (&error));
     921         [ #  # ]:           0 :       return;
     922                 :             :     }
     923                 :             : 
     924                 :           0 :   tracker_sparql_cursor_next_async (cursor,
     925                 :             :                                     cancellable,
     926                 :             :                                     (GAsyncReadyCallback) cursor_search_messages_cb,
     927                 :             :                                     g_object_ref (task));
     928                 :             : }
     929                 :             : 
     930                 :             : /**
     931                 :             :  * valent_messages_adapter_search:
     932                 :             :  * @adapter: a `ValentMessagesAdapter`
     933                 :             :  * @query: a string to search for
     934                 :             :  * @cancellable: (nullable): a `GCancellable`
     935                 :             :  * @callback: (scope async): a `GAsyncReadyCallback`
     936                 :             :  * @user_data: user supplied data
     937                 :             :  *
     938                 :             :  * Search through all the messages in @adapter and return the most recent message
     939                 :             :  * from each thread containing @query.
     940                 :             :  *
     941                 :             :  * Call [method@Valent.MessagesAdapter.search_finish] to get the result.
     942                 :             :  *
     943                 :             :  * Since: 1.0
     944                 :             :  */
     945                 :             : void
     946                 :           0 : valent_messages_adapter_search (ValentMessagesAdapter *adapter,
     947                 :             :                                 const char            *query,
     948                 :             :                                 GCancellable          *cancellable,
     949                 :             :                                 GAsyncReadyCallback    callback,
     950                 :             :                                 gpointer               user_data)
     951                 :             : {
     952                 :           0 :   g_autoptr (TrackerSparqlStatement) stmt = NULL;
     953         [ #  # ]:           0 :   g_autoptr (GTask) task = NULL;
     954         [ #  # ]:           0 :   g_autofree char *query_sanitized = NULL;
     955                 :           0 :   GError *error = NULL;
     956                 :             : 
     957                 :           0 :   VALENT_ENTRY;
     958                 :             : 
     959         [ #  # ]:           0 :   g_return_if_fail (VALENT_IS_MESSAGES_ADAPTER (adapter));
     960         [ #  # ]:           0 :   g_return_if_fail (query != NULL);
     961   [ #  #  #  #  :           0 :   g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
             #  #  #  # ]
     962                 :             : 
     963                 :           0 :   task = g_task_new (adapter, cancellable, callback, user_data);
     964         [ #  # ]:           0 :   g_task_set_source_tag (task, valent_messages_adapter_search);
     965                 :           0 :   g_task_set_task_data (task, g_list_store_new (VALENT_TYPE_MESSAGE), g_object_unref);
     966                 :             : 
     967                 :           0 :   stmt = g_object_dup_data (G_OBJECT (adapter),
     968                 :             :                             "valent-message-adapter-search",
     969                 :             :                             _g_object_dup0,
     970                 :             :                             NULL);
     971                 :             : 
     972         [ #  # ]:           0 :   if (stmt == NULL)
     973                 :             :     {
     974                 :           0 :       g_autoptr (TrackerSparqlConnection) connection = NULL;
     975                 :             : 
     976                 :           0 :       g_object_get (adapter, "connection", &connection, NULL);
     977                 :           0 :       stmt = tracker_sparql_connection_load_statement_from_gresource (connection,
     978                 :             :                                                                       SEARCH_MESSAGES_RQ,
     979                 :             :                                                                       cancellable,
     980                 :             :                                                                       &error);
     981                 :             : 
     982         [ #  # ]:           0 :       if (stmt == NULL)
     983                 :             :         {
     984                 :           0 :           g_task_return_error (task, g_steal_pointer (&error));
     985         [ #  # ]:           0 :           return;
     986                 :             :         }
     987                 :             : 
     988         [ #  # ]:           0 :       g_object_set_data_full (G_OBJECT (adapter),
     989                 :             :                               "valent-message-adapter-search",
     990                 :             :                               g_object_ref (stmt),
     991                 :             :                               g_object_unref);
     992                 :             :     }
     993                 :             : 
     994                 :           0 :   query_sanitized = tracker_sparql_escape_string (query);
     995                 :           0 :   tracker_sparql_statement_bind_string (stmt, "query", query_sanitized);
     996                 :           0 :   tracker_sparql_statement_execute_async (stmt,
     997                 :             :                                           g_task_get_cancellable (task),
     998                 :             :                                           (GAsyncReadyCallback) execute_search_messages_cb,
     999                 :             :                                           g_object_ref (task));
    1000                 :             : 
    1001                 :           0 :   VALENT_EXIT;
    1002                 :             : }
    1003                 :             : 
    1004                 :             : /**
    1005                 :             :  * valent_messages_adapter_search_finish:
    1006                 :             :  * @adapter: a `ValentMessagesAdapter`
    1007                 :             :  * @result: a `GAsyncResult`
    1008                 :             :  * @error: (nullable): a `GError`
    1009                 :             :  *
    1010                 :             :  * Finish an operation started by [method@Valent.MessagesAdapter.search].
    1011                 :             :  *
    1012                 :             :  * Returns: (transfer full) (element-type Valent.Message): a list of messages
    1013                 :             :  *
    1014                 :             :  * Since: 1.0
    1015                 :             :  */
    1016                 :             : GListModel *
    1017                 :           0 : valent_messages_adapter_search_finish (ValentMessagesAdapter  *adapter,
    1018                 :             :                                        GAsyncResult           *result,
    1019                 :             :                                        GError                **error)
    1020                 :             : {
    1021                 :           0 :   GListModel *ret;
    1022                 :             : 
    1023                 :           0 :   VALENT_ENTRY;
    1024                 :             : 
    1025         [ #  # ]:           0 :   g_return_val_if_fail (VALENT_IS_MESSAGES_ADAPTER (adapter), NULL);
    1026         [ #  # ]:           0 :   g_return_val_if_fail (g_task_is_valid (result, adapter), NULL);
    1027   [ #  #  #  # ]:           0 :   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
    1028                 :             : 
    1029                 :           0 :   ret = g_task_propagate_pointer (G_TASK (result), error);
    1030                 :             : 
    1031                 :           0 :   VALENT_RETURN (ret);
    1032                 :             : }
    1033                 :             : 
        

Generated by: LCOV version 2.0-1