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

Generated by: LCOV version 2.0-1