LCOV - code coverage report
Current view: top level - src/plugins/mpris - vdp-mpris-adapter.c (source / functions) Coverage Total Hit
Test: Code Coverage Lines: 88.7 % 159 141
Test Date: 2024-12-21 23:29:11 Functions: 100.0 % 16 16
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 48.0 % 100 48

             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 "vdp-mpris-adapter"
       5                 :             : 
       6                 :             : #include "config.h"
       7                 :             : 
       8                 :             : #include <gio/gio.h>
       9                 :             : #include <valent.h>
      10                 :             : 
      11                 :             : #include "vdp-mpris-player.h"
      12                 :             : 
      13                 :             : #include "vdp-mpris-adapter.h"
      14                 :             : 
      15                 :             : struct _VdpMprisAdapter
      16                 :             : {
      17                 :             :   ValentMediaAdapter  parent_instance;
      18                 :             : 
      19                 :             :   ValentDevice       *device;
      20                 :             :   GCancellable       *cancellable;
      21                 :             :   GHashTable         *players;
      22                 :             :   GHashTable         *transfers;
      23                 :             : };
      24                 :             : 
      25   [ +  +  +  - ]:          19 : G_DEFINE_FINAL_TYPE (VdpMprisAdapter, vdp_mpris_adapter, VALENT_TYPE_MEDIA_ADAPTER)
      26                 :             : 
      27                 :             : static inline void
      28                 :           4 : valent_device_send_packet_cb (ValentDevice *device,
      29                 :             :                               GAsyncResult *result,
      30                 :             :                               gpointer      user_data)
      31                 :             : {
      32                 :           8 :   g_autoptr (GError) error = NULL;
      33                 :             : 
      34         [ -  + ]:           4 :   if (!valent_device_send_packet_finish (device, result, &error))
      35                 :             :     {
      36         [ #  # ]:           0 :       if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED))
      37                 :           0 :         g_critical ("%s(): %s", G_STRFUNC, error->message);
      38         [ #  # ]:           0 :       else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_CONNECTED))
      39                 :           0 :         g_warning ("%s(): %s", G_STRFUNC, error->message);
      40         [ #  # ]:           0 :       else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
      41                 :           0 :         g_debug ("%s(): %s", G_STRFUNC, error->message);
      42                 :             :     }
      43                 :           4 : }
      44                 :             : 
      45                 :             : static void
      46                 :           1 : receive_art_cb (ValentTransfer  *transfer,
      47                 :             :                 GAsyncResult    *result,
      48                 :             :                 VdpMprisAdapter *self)
      49                 :             : {
      50                 :           1 :   g_autoptr (JsonNode) packet = NULL;
      51   [ -  -  +  - ]:           1 :   g_autoptr (GFile) file = NULL;
      52   [ -  -  +  - ]:           1 :   g_autoptr (GError) error = NULL;
      53                 :           1 :   ValentMediaPlayer *player = NULL;
      54                 :           1 :   const char *name;
      55                 :             : 
      56         [ -  + ]:           1 :   if (!valent_transfer_execute_finish (transfer, result, &error))
      57                 :             :     {
      58         [ #  # ]:           0 :       if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
      59                 :           0 :         g_warning ("%s(): %s", G_STRFUNC, error->message);
      60                 :             : 
      61         [ #  # ]:           0 :       return;
      62                 :             :     }
      63                 :             : 
      64                 :           1 :   g_object_get (transfer,
      65                 :             :                 "file",   &file,
      66                 :             :                 "packet", &packet,
      67                 :             :                 NULL);
      68                 :             : 
      69         [ +  - ]:           1 :   if (valent_packet_get_string (packet, "player", &name))
      70                 :           1 :     player = g_hash_table_lookup (self->players, name);;
      71                 :             : 
      72         [ +  - ]:           1 :   if (player != NULL)
      73                 :           1 :     vdp_mpris_player_update_art (VDP_MPRIS_PLAYER (player), file);
      74                 :             : }
      75                 :             : 
      76                 :             : static void
      77                 :           1 : vdp_mpris_adapter_receive_album_art (VdpMprisAdapter *self,
      78                 :             :                                      JsonNode        *packet)
      79                 :             : {
      80                 :           1 :   ValentContext *context = NULL;
      81                 :           1 :   const char *url;
      82                 :           1 :   g_autofree char *filename = NULL;
      83                 :           1 :   g_autoptr (GFile) file = NULL;
      84         [ +  - ]:           1 :   g_autoptr (ValentTransfer) transfer = NULL;
      85                 :             : 
      86         [ -  + ]:           1 :   if (!valent_packet_get_string (packet, "albumArtUrl", &url))
      87                 :             :     {
      88                 :           0 :       g_debug ("%s(): expected \"albumArtUrl\" field holding a string",
      89                 :             :                G_STRFUNC);
      90                 :           0 :       return;
      91                 :             :     }
      92                 :             : 
      93                 :           1 :   context = valent_device_get_context (self->device);
      94                 :           1 :   filename = g_compute_checksum_for_string (G_CHECKSUM_MD5, url, -1);
      95                 :           1 :   file = valent_context_get_cache_file (context, filename);
      96                 :             : 
      97                 :           1 :   transfer = valent_device_transfer_new (self->device, packet, file);
      98         [ +  - ]:           1 :   valent_transfer_execute (transfer,
      99                 :             :                            self->cancellable,
     100                 :             :                            (GAsyncReadyCallback)receive_art_cb,
     101                 :             :                            self);
     102                 :             : }
     103                 :             : 
     104                 :             : /*< private >
     105                 :             :  * @self: a `VdpMprisAdapter`
     106                 :             :  * @player: a
     107                 :             :  *
     108                 :             :  * Send a request for messages starting at @range_start_timestamp in
     109                 :             :  * oldest-to-newest order, for a maximum of @number_to_request.
     110                 :             :  */
     111                 :             : static void
     112                 :           3 : vdp_mpris_adapter_request_player_list (VdpMprisAdapter *self)
     113                 :             : {
     114                 :           6 :   g_autoptr (JsonBuilder) builder = NULL;
     115         [ -  + ]:           3 :   g_autoptr (JsonNode) packet = NULL;
     116                 :             : 
     117                 :           3 :   valent_packet_init (&builder, "kdeconnect.mpris.request");
     118                 :           3 :   json_builder_set_member_name (builder, "requestPlayerList");
     119                 :           3 :   json_builder_add_boolean_value (builder, TRUE);
     120                 :           3 :   packet = valent_packet_end (&builder);
     121                 :             : 
     122         [ +  - ]:           3 :   valent_device_send_packet (self->device,
     123                 :             :                              packet,
     124                 :             :                              self->cancellable,
     125                 :             :                              (GAsyncReadyCallback) valent_device_send_packet_cb,
     126                 :             :                              NULL);
     127                 :           3 : }
     128                 :             : 
     129                 :             : /*< private >
     130                 :             :  * @self: a `VdpMprisAdapter`
     131                 :             :  * @player: a player name
     132                 :             :  *
     133                 :             :  * Request an update for for @player.
     134                 :             :  */
     135                 :             : static void
     136                 :           1 : vdp_mpris_adapter_request_update (VdpMprisAdapter *self,
     137                 :             :                                   const char      *player)
     138                 :             : {
     139                 :           2 :   g_autoptr (JsonBuilder) builder = NULL;
     140         [ -  + ]:           1 :   g_autoptr (JsonNode) packet = NULL;
     141                 :             : 
     142         [ +  - ]:           1 :   g_assert (VDP_IS_MPRIS_ADAPTER (self));
     143         [ -  + ]:           1 :   g_assert (player != NULL);
     144                 :             : 
     145                 :           1 :   valent_packet_init (&builder, "kdeconnect.mpris.request");
     146                 :           1 :   json_builder_set_member_name (builder, "player");
     147                 :           1 :   json_builder_add_string_value (builder, player);
     148                 :           1 :   json_builder_set_member_name (builder, "requestNowPlaying");
     149                 :           1 :   json_builder_add_boolean_value (builder, TRUE);
     150                 :           1 :   json_builder_set_member_name (builder, "requestVolume");
     151                 :           1 :   json_builder_add_boolean_value (builder, TRUE);
     152                 :           1 :   packet = valent_packet_end (&builder);
     153                 :             : 
     154         [ +  - ]:           1 :   valent_device_send_packet (self->device,
     155                 :             :                              packet,
     156                 :             :                              self->cancellable,
     157                 :             :                              (GAsyncReadyCallback) valent_device_send_packet_cb,
     158                 :             :                              NULL);
     159                 :           1 : }
     160                 :             : 
     161                 :             : /*
     162                 :             :  * ValentMediaAdapter
     163                 :             :  */
     164                 :             : static void
     165                 :           6 : on_device_state_changed (ValentDevice    *device,
     166                 :             :                          GParamSpec      *pspec,
     167                 :             :                          VdpMprisAdapter *self)
     168                 :             : {
     169                 :           6 :   ValentDeviceState state = VALENT_DEVICE_STATE_NONE;
     170                 :           6 :   gboolean available;
     171                 :             : 
     172                 :           6 :   state = valent_device_get_state (device);
     173                 :           6 :   available = (state & VALENT_DEVICE_STATE_CONNECTED) != 0 &&
     174                 :             :               (state & VALENT_DEVICE_STATE_PAIRED) != 0;
     175                 :             : 
     176   [ +  -  +  + ]:           6 :   if (available && self->cancellable == NULL)
     177                 :             :     {
     178                 :           9 :       g_autoptr (GCancellable) cancellable = NULL;
     179                 :             : 
     180                 :           3 :       cancellable = g_cancellable_new ();
     181                 :           3 :       self->cancellable = valent_object_chain_cancellable (VALENT_OBJECT (self),
     182                 :             :                                                            cancellable);
     183         [ +  - ]:           3 :       vdp_mpris_adapter_request_player_list (self);
     184                 :             :     }
     185         [ #  # ]:           0 :   else if (!available && self->cancellable != NULL)
     186                 :             :     {
     187                 :           0 :       g_hash_table_remove_all (self->players);
     188                 :           0 :       g_cancellable_cancel (self->cancellable);
     189         [ #  # ]:           0 :       g_clear_object (&self->cancellable);
     190                 :             :     }
     191                 :           6 : }
     192                 :             : 
     193                 :             : /*
     194                 :             :  * GObject
     195                 :             :  */
     196                 :             : static void
     197                 :           3 : vdp_mpris_adapter_constructed (GObject *object)
     198                 :             : {
     199                 :           3 :   VdpMprisAdapter *self = VDP_MPRIS_ADAPTER (object);
     200                 :             : 
     201                 :           3 :   G_OBJECT_CLASS (vdp_mpris_adapter_parent_class)->constructed (object);
     202                 :             : 
     203                 :           3 :   self->device = valent_resource_get_source (VALENT_RESOURCE (self));
     204                 :           3 :   g_signal_connect_object (self->device,
     205                 :             :                            "notify::state",
     206                 :             :                            G_CALLBACK (on_device_state_changed),
     207                 :             :                            self,
     208                 :             :                            G_CONNECT_DEFAULT);
     209                 :           3 :   on_device_state_changed (self->device, NULL, self);
     210                 :           3 : }
     211                 :             : 
     212                 :             : static void
     213                 :           3 : vdp_mpris_adapter_finalize (GObject *object)
     214                 :             : {
     215                 :           3 :   VdpMprisAdapter *self = VDP_MPRIS_ADAPTER (object);
     216                 :             : 
     217         [ +  - ]:           3 :   g_clear_object (&self->cancellable);
     218         [ +  - ]:           3 :   g_clear_pointer (&self->players, g_hash_table_unref);
     219         [ +  - ]:           3 :   g_clear_pointer (&self->transfers, g_hash_table_unref);
     220                 :             : 
     221                 :           3 :   G_OBJECT_CLASS (vdp_mpris_adapter_parent_class)->finalize (object);
     222                 :           3 : }
     223                 :             : 
     224                 :             : static void
     225                 :           1 : vdp_mpris_adapter_class_init (VdpMprisAdapterClass *klass)
     226                 :             : {
     227                 :           1 :   GObjectClass *object_class = G_OBJECT_CLASS (klass);
     228                 :             : 
     229                 :           1 :   object_class->constructed = vdp_mpris_adapter_constructed;
     230                 :           1 :   object_class->finalize = vdp_mpris_adapter_finalize;
     231                 :             : }
     232                 :             : 
     233                 :             : static void
     234                 :           3 : vdp_mpris_adapter_init (VdpMprisAdapter *self)
     235                 :             : {
     236                 :           3 :   self->players = g_hash_table_new_full (g_str_hash,
     237                 :             :                                          g_str_equal,
     238                 :             :                                          g_free,
     239                 :             :                                          g_object_unref);
     240                 :           3 :   self->transfers = g_hash_table_new_full (g_str_hash,
     241                 :             :                                            g_str_equal,
     242                 :             :                                            g_free,
     243                 :             :                                            g_object_unref);
     244                 :           3 : }
     245                 :             : 
     246                 :             : /**
     247                 :             :  * vdp_mpris_adapter_new:
     248                 :             :  * @device: a `ValentDevice`
     249                 :             :  *
     250                 :             :  * Create a new `VdpMprisAdapter`.
     251                 :             :  *
     252                 :             :  * Returns: (transfer full): a new message store
     253                 :             :  */
     254                 :             : ValentMediaAdapter *
     255                 :           3 : vdp_mpris_adapter_new (ValentDevice *device)
     256                 :             : {
     257                 :           6 :   g_autoptr (ValentContext) context = NULL;
     258         [ +  - ]:           3 :   g_autofree char *iri = NULL;
     259                 :             : 
     260         [ +  - ]:           3 :   g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
     261                 :             : 
     262                 :           3 :   context = valent_context_new (valent_device_get_context (device),
     263                 :             :                                 "plugin",
     264                 :             :                                 "mpris");
     265                 :           3 :   iri = tracker_sparql_escape_uri_printf ("urn:valent:messages:%s",
     266                 :             :                                           valent_device_get_id (device));
     267                 :           3 :   return g_object_new (VDP_TYPE_MPRIS_ADAPTER,
     268                 :             :                        "iri",     iri,
     269                 :             :                        "context", context,
     270                 :             :                        "source",  device,
     271                 :             :                        "title",   valent_device_get_name (device),
     272                 :             :                        NULL);
     273                 :             : }
     274                 :             : 
     275                 :             : static void
     276                 :           2 : vdp_mpris_adapter_handle_player_list (VdpMprisAdapter *self,
     277                 :             :                                       JsonArray       *player_list)
     278                 :             : {
     279                 :           2 :   GHashTableIter iter;
     280                 :           2 :   VdpMprisPlayer *player;
     281                 :           2 :   const char *name;
     282                 :           2 :   unsigned int n_remote = 0;
     283                 :           4 :   g_autofree const char **remote_names = NULL;
     284                 :             : 
     285         [ +  - ]:           2 :   g_assert (VDP_IS_MPRIS_ADAPTER (self));
     286         [ -  + ]:           2 :   g_assert (player_list != NULL);
     287                 :             : 
     288                 :             : #ifndef __clang_analyzer__
     289                 :             :   /* Collect the remote player names */
     290                 :           2 :   n_remote = json_array_get_length (player_list);
     291         [ -  + ]:           2 :   remote_names = g_new0 (const char *, n_remote + 1);
     292                 :             : 
     293         [ +  + ]:           3 :   for (unsigned int i = 0; i < n_remote; i++)
     294                 :           1 :     remote_names[i] = json_array_get_string_element (player_list, i);
     295                 :             : 
     296                 :             :   /* Remove old players */
     297                 :           2 :   g_hash_table_iter_init (&iter, self->players);
     298         [ +  + ]:           5 :   while (g_hash_table_iter_next (&iter, (void **)&name, (void **)&player))
     299                 :             :     {
     300         [ +  - ]:           1 :       if (!g_strv_contains (remote_names, name))
     301                 :             :         {
     302                 :           1 :           g_hash_table_iter_remove (&iter);
     303                 :           1 :           valent_object_destroy (VALENT_OBJECT (player));
     304                 :             :         }
     305                 :             :     }
     306                 :             : 
     307                 :             :   /* Add new players */
     308         [ +  + ]:           3 :   for (unsigned int i = 0; remote_names[i] != NULL; i++)
     309                 :             :     {
     310                 :           1 :       name = remote_names[i];
     311         [ -  + ]:           1 :       if (g_hash_table_contains (self->players, name))
     312                 :           0 :         continue;
     313                 :             : 
     314                 :           1 :       player = vdp_mpris_player_new (self->device);
     315                 :           1 :       vdp_mpris_player_update_name (player, name);
     316                 :             : 
     317                 :           1 :       valent_media_adapter_player_added (VALENT_MEDIA_ADAPTER (self),
     318                 :             :                                          VALENT_MEDIA_PLAYER (player));
     319                 :           1 :       valent_media_export_player (valent_media_get_default (),
     320                 :             :                                   VALENT_MEDIA_PLAYER (player));
     321         [ -  + ]:           1 :       g_hash_table_insert (self->players,
     322                 :           1 :                            g_strdup (name),
     323                 :             :                            g_steal_pointer (&player));
     324                 :             : 
     325                 :           1 :       vdp_mpris_adapter_request_update (self, name);
     326                 :             :     }
     327                 :             : #endif /* __clang_analyzer__ */
     328                 :           2 : }
     329                 :             : 
     330                 :             : static void
     331                 :           3 : vdp_mpris_adapter_handle_player_update (VdpMprisAdapter *self,
     332                 :             :                                         JsonNode        *packet)
     333                 :             : {
     334                 :           3 :   ValentMediaPlayer *player = NULL;
     335                 :           3 :   const char *name;
     336                 :             : 
     337                 :             :   /* Get the remote */
     338         [ +  - ]:           3 :   if (valent_packet_get_string (packet, "player", &name))
     339                 :           3 :     player = g_hash_table_lookup (self->players, name);
     340                 :             : 
     341         [ -  + ]:           3 :   if (player == NULL)
     342                 :             :     {
     343                 :           0 :       vdp_mpris_adapter_request_player_list (self);
     344                 :           1 :       return;
     345                 :             :     }
     346                 :             : 
     347         [ +  + ]:           3 :   if (valent_packet_check_field (packet, "transferringAlbumArt"))
     348                 :             :     {
     349                 :           1 :       vdp_mpris_adapter_receive_album_art (self, packet);
     350                 :           1 :       return;
     351                 :             :     }
     352                 :             : 
     353                 :           2 :   vdp_mpris_player_handle_packet (VDP_MPRIS_PLAYER (player), packet);
     354                 :             : }
     355                 :             : 
     356                 :             : /**
     357                 :             :  * vdp_mpris_adapter_handle_packet:
     358                 :             :  * @self: a `VdpMprisAdapter`
     359                 :             :  * @packet: a `kdeconnect.sms.attachment_file` packet
     360                 :             :  *
     361                 :             :  * Handle an attachment file.
     362                 :             :  */
     363                 :             : void
     364                 :           5 : vdp_mpris_adapter_handle_packet (VdpMprisAdapter *self,
     365                 :             :                                  JsonNode        *packet)
     366                 :             : {
     367                 :           5 :   JsonArray *player_list;
     368                 :             : 
     369         [ +  - ]:           5 :   g_assert (VDP_IS_MPRIS_ADAPTER (self));
     370         [ -  + ]:           5 :   g_assert (VALENT_IS_PACKET (packet));
     371                 :             : 
     372         [ +  + ]:           5 :   if (valent_packet_get_array (packet, "playerList", &player_list))
     373                 :           2 :     vdp_mpris_adapter_handle_player_list (self, player_list);
     374         [ +  - ]:           3 :   else if (valent_packet_get_string (packet, "player", NULL))
     375                 :           3 :     vdp_mpris_adapter_handle_player_update (self, packet);
     376                 :             :   else
     377                 :           0 :     g_assert_not_reached ();
     378                 :           5 : }
     379                 :             : 
        

Generated by: LCOV version 2.0-1