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 : if (!valent_object_in_destruction (VALENT_OBJECT (data)))
31 : 1 : valent_object_destroy (VALENT_OBJECT (data));
32 : :
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 [ - + ]: 1 : if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED))
46 : 0 : g_critical ("%s(): %s", G_STRFUNC, error->message);
47 [ - + ]: 1 : 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 [ - + ]: 1 : 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 : 9 : on_device_state_changed (ValentDevice *device,
175 : : GParamSpec *pspec,
176 : : VdpMprisAdapter *self)
177 : : {
178 : 9 : ValentDeviceState state = VALENT_DEVICE_STATE_NONE;
179 : 9 : gboolean available;
180 : :
181 : 9 : state = valent_device_get_state (device);
182 [ + + ]: 9 : available = (state & VALENT_DEVICE_STATE_CONNECTED) != 0 &&
183 [ + - ]: 6 : (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 [ + - ]: 3 : else if (!available && self->cancellable != NULL)
191 : : {
192 : 3 : g_cancellable_cancel (self->cancellable);
193 [ + - ]: 3 : g_clear_object (&self->cancellable);
194 : 3 : g_hash_table_remove_all (self->players);
195 : : }
196 : 9 : }
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_object_get_parent (VALENT_OBJECT (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 : 2 : vdp_mpris_adapter_finalize (GObject *object)
219 : : {
220 : 2 : VdpMprisAdapter *self = VDP_MPRIS_ADAPTER (object);
221 : :
222 [ - + ]: 2 : g_clear_object (&self->cancellable);
223 [ + - ]: 2 : g_clear_pointer (&self->players, g_hash_table_unref);
224 [ + - ]: 2 : g_clear_pointer (&self->transfers, g_hash_table_unref);
225 : :
226 : 2 : G_OBJECT_CLASS (vdp_mpris_adapter_parent_class)->finalize (object);
227 : 2 : }
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 : : "parent", device,
275 : : NULL);
276 : : }
277 : :
278 : : static void
279 : 2 : vdp_mpris_adapter_handle_player_list (VdpMprisAdapter *self,
280 : : JsonArray *player_list)
281 : : {
282 : 2 : GHashTableIter iter;
283 : 2 : VdpMprisPlayer *player;
284 : 2 : const char *name;
285 : 2 : unsigned int n_remote = 0;
286 : 4 : g_autofree const char **remote_names = NULL;
287 : :
288 [ - + ]: 2 : g_assert (VDP_IS_MPRIS_ADAPTER (self));
289 [ + - ]: 2 : g_assert (player_list != NULL);
290 : :
291 : : #ifndef __clang_analyzer__
292 : : /* Collect the remote player names */
293 : 2 : n_remote = json_array_get_length (player_list);
294 [ - + ]: 2 : remote_names = g_new0 (const char *, n_remote + 1);
295 : :
296 [ + + ]: 3 : for (unsigned int i = 0; i < n_remote; i++)
297 : 1 : remote_names[i] = json_array_get_string_element (player_list, i);
298 : :
299 : : /* Remove old players */
300 : 2 : g_hash_table_iter_init (&iter, self->players);
301 [ + + ]: 5 : while (g_hash_table_iter_next (&iter, (void **)&name, (void **)&player))
302 : : {
303 [ + - ]: 1 : if (!g_strv_contains (remote_names, name))
304 : 1 : g_hash_table_iter_remove (&iter);
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 : :
|