No issues found
Tool | Failure ID | Location | Function | Message | Data |
---|---|---|---|---|---|
clang-analyzer | no-output-found | rb-metadata-dbus-client.c | Message(text='Unable to locate XML output from invoke-clang-analyzer') | None | |
clang-analyzer | no-output-found | rb-metadata-dbus-client.c | Message(text='Unable to locate XML output from invoke-clang-analyzer') | None |
1 /*
2 * Copyright (C) 2006 Jonathan Matthew <jonathan@kaolin.hn.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * The Rhythmbox authors hereby grant permission for non-GPL compatible
10 * GStreamer plugins to be used and distributed together with GStreamer
11 * and Rhythmbox. This permission is above and beyond the permissions granted
12 * by the GPL license by which Rhythmbox is covered. If you modify this code
13 * you may extend this exception to your version of the code, but you are not
14 * obligated to do so. If you do not wish to do so, delete this exception
15 * statement from your version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
25 *
26 */
27
28 /*
29 * Client for out-of-process metadata reader communicating via D-BUS.
30 *
31 * How this works:
32 * - spawn rb-metadata process, with pipes
33 * - child process sets up its dbus server or whatever
34 * - if successful, child writes dbus server address to stdout; otherwise, dies.
35 * - parent opens dbus connection
36 *
37 * For each request, the parent checks if the dbus connection is still alive,
38 * and pings the child to see if it's still responding. If the child has
39 * exited or is not responding, the parent starts a new metadata helper as
40 * described above.
41 *
42 * The child process exits after a certain period of inactivity (30s
43 * currently), so the ping message serves two purposes - it checks that the
44 * child is still capable of handling messages, and it ensures the child
45 * doesn't time out between when we check the child is still running and when
46 * we actually send it the request.
47 */
48
49 /**
50 * SECTION:rb-metadata
51 * @short_description: metadata reader and writer interface
52 *
53 * Provides a simple synchronous interface for metadata extraction and updating.
54 */
55
56 #include <config.h>
57
58 #include "rb-metadata.h"
59 #include "rb-metadata-dbus.h"
60 #include "rb-debug.h"
61 #include "rb-util.h"
62
63 #include <glib/gi18n.h>
64 #include <gio/gio.h>
65
66 #include <unistd.h>
67 #include <sys/types.h>
68 #include <sys/signal.h>
69 #include <sys/wait.h>
70 #include <string.h>
71 #include <stdlib.h>
72
73 static void rb_metadata_class_init (RBMetaDataClass *klass);
74 static void rb_metadata_init (RBMetaData *md);
75 static void rb_metadata_finalize (GObject *object);
76
77 static gboolean tried_env_address = FALSE;
78 static GDBusConnection *dbus_connection = NULL;
79 static GPid metadata_child = 0;
80 static int metadata_stdout = -1;
81 static GMainContext *main_context = NULL;
82 static GStaticMutex conn_mutex = G_STATIC_MUTEX_INIT;
83 static char **saveable_types = NULL;
84
85 struct RBMetaDataPrivate
86 {
87 char *media_type;
88 char **missing_plugins;
89 char **plugin_descriptions;
90 gboolean has_audio;
91 gboolean has_video;
92 gboolean has_other_data;
93 GHashTable *metadata;
94 };
95
96 G_DEFINE_TYPE (RBMetaData, rb_metadata, G_TYPE_OBJECT)
97
98 static void
99 rb_metadata_class_init (RBMetaDataClass *klass)
100 {
101 GObjectClass *object_class = G_OBJECT_CLASS (klass);
102
103 object_class->finalize = rb_metadata_finalize;
104
105 g_type_class_add_private (object_class, sizeof (RBMetaDataPrivate));
106
107 main_context = g_main_context_new (); /* maybe not needed? */
108 }
109
110 static void
111 rb_metadata_init (RBMetaData *md)
112 {
113 md->priv = G_TYPE_INSTANCE_GET_PRIVATE (md, RB_TYPE_METADATA, RBMetaDataPrivate);
114 }
115
116 static void
117 rb_metadata_finalize (GObject *object)
118 {
119 RBMetaData *md;
120
121 md = RB_METADATA (object);
122
123 g_free (md->priv->media_type);
124 if (md->priv->metadata)
125 g_hash_table_destroy (md->priv->metadata);
126
127 G_OBJECT_CLASS (rb_metadata_parent_class)->finalize (object);
128 }
129
130 /**
131 * rb_metadata_new:
132 *
133 * Creates a new metadata backend instance.
134 *
135 * Return value: new #RBMetaData instance
136 */
137 RBMetaData *
138 rb_metadata_new (void)
139 {
140 return RB_METADATA (g_object_new (RB_TYPE_METADATA, NULL));
141 }
142
143 static void
144 kill_metadata_service (void)
145 {
146 if (dbus_connection) {
147 if (g_dbus_connection_is_closed (dbus_connection) == FALSE) {
148 rb_debug ("closing dbus connection");
149 g_dbus_connection_close_sync (dbus_connection, NULL, NULL);
150 } else {
151 rb_debug ("dbus connection already closed");
152 }
153 g_object_unref (dbus_connection);
154 dbus_connection = NULL;
155 }
156
157 if (metadata_child) {
158 rb_debug ("killing child process");
159 kill (metadata_child, SIGINT);
160 g_spawn_close_pid (metadata_child);
161 metadata_child = 0;
162 }
163
164 if (metadata_stdout != -1) {
165 rb_debug ("closing metadata child process stdout pipe");
166 close (metadata_stdout);
167 metadata_stdout = -1;
168 }
169 }
170
171 static gboolean
172 ping_metadata_service (GError **error)
173 {
174 GDBusMessage *message;
175 GDBusMessage *response;
176
177 if (g_dbus_connection_is_closed (dbus_connection))
178 return FALSE;
179
180 message = g_dbus_message_new_method_call (RB_METADATA_DBUS_NAME,
181 RB_METADATA_DBUS_OBJECT_PATH,
182 RB_METADATA_DBUS_INTERFACE,
183 "ping");
184 response = g_dbus_connection_send_message_with_reply_sync (dbus_connection,
185 message,
186 G_DBUS_MESSAGE_FLAGS_NONE,
187 RB_METADATA_DBUS_TIMEOUT,
188 NULL,
189 NULL,
190 error);
191 g_object_unref (message);
192
193 if (*error != NULL) {
194 /* ignore 'no reply', just means the service is dead */
195 if ((*error)->domain == G_DBUS_ERROR && (*error)->code == G_DBUS_ERROR_NO_REPLY) {
196 g_clear_error (error);
197 }
198 return FALSE;
199 }
200 g_object_unref (response);
201 return TRUE;
202 }
203
204 static gboolean
205 start_metadata_service (GError **error)
206 {
207 GIOChannel *stdout_channel;
208 GIOStatus status;
209 gchar *dbus_address = NULL;
210 char *saveable_type_list;
211 GVariant *response_body;
212
213 if (dbus_connection) {
214 if (ping_metadata_service (error))
215 return TRUE;
216
217 /* Metadata service is broken. Kill it, and if we haven't run
218 * into any errors yet, we can try to restart it.
219 */
220 kill_metadata_service ();
221
222 if (*error)
223 return FALSE;
224 }
225
226 if (!tried_env_address) {
227 const char *addr = g_getenv ("RB_DBUS_METADATA_ADDRESS");
228 tried_env_address = TRUE;
229 if (addr) {
230 rb_debug ("trying metadata service address %s (from environment)", addr);
231 dbus_address = g_strdup (addr);
232 metadata_child = 0;
233 }
234 }
235
236 if (dbus_address == NULL) {
237 GPtrArray *argv;
238 gboolean res;
239 char **debug_args;
240 GError *local_error;
241 int i;
242
243 argv = g_ptr_array_new ();
244 /*
245 * Normally, we find the metadata helper in the libexec dir,
246 * but when --enable-uninstalled-build is specified, we look
247 * in the directory it's built in.
248 */
249 #ifdef USE_UNINSTALLED_DIRS
250 g_ptr_array_add (argv, METADATA_UNINSTALLED_DIR "/rhythmbox-metadata");
251 #else
252 g_ptr_array_add (argv, LIBEXEC_DIR G_DIR_SEPARATOR_S INSTALLED_METADATA_HELPER);
253 #endif
254 debug_args = rb_debug_get_args ();
255 i = 0;
256 while (debug_args[i] != NULL) {
257 g_ptr_array_add (argv, debug_args[i]);
258 i++;
259 }
260
261 g_ptr_array_add (argv, "unix:tmpdir=/tmp");
262 g_ptr_array_add (argv, NULL);
263
264 local_error = NULL;
265 res = g_spawn_async_with_pipes (NULL,
266 (char **)argv->pdata,
267 NULL,
268 0,
269 NULL, NULL,
270 &metadata_child,
271 NULL,
272 &metadata_stdout,
273 NULL,
274 &local_error);
275 g_ptr_array_free (argv, TRUE);
276 g_strfreev (debug_args);
277
278 if (res == FALSE) {
279 g_propagate_error (error, local_error);
280 return FALSE;
281 }
282
283 stdout_channel = g_io_channel_unix_new (metadata_stdout);
284 status = g_io_channel_read_line (stdout_channel, &dbus_address, NULL, NULL, error);
285 g_io_channel_unref (stdout_channel);
286 if (status != G_IO_STATUS_NORMAL) {
287 kill_metadata_service ();
288 return FALSE;
289 }
290
291 g_strchomp (dbus_address);
292 rb_debug ("Got metadata helper D-BUS address %s", dbus_address);
293 }
294
295 dbus_connection = g_dbus_connection_new_for_address_sync (dbus_address,
296 G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
297 NULL,
298 NULL,
299 error);
300 g_free (dbus_address);
301 if (*error != NULL) {
302 kill_metadata_service ();
303 return FALSE;
304 }
305
306 g_dbus_connection_set_exit_on_close (dbus_connection, FALSE);
307
308 rb_debug ("Metadata process %d started", metadata_child);
309
310 /* now ask it what types it can re-tag */
311 if (saveable_types != NULL) {
312 g_strfreev (saveable_types);
313 }
314
315 response_body = g_dbus_connection_call_sync (dbus_connection,
316 RB_METADATA_DBUS_NAME,
317 RB_METADATA_DBUS_OBJECT_PATH,
318 RB_METADATA_DBUS_INTERFACE,
319 "getSaveableTypes",
320 NULL,
321 NULL,
322 G_DBUS_CALL_FLAGS_NONE,
323 RB_METADATA_DBUS_TIMEOUT,
324 NULL,
325 error);
326 if (response_body == NULL) {
327 rb_debug ("saveable type query failed: %s", (*error)->message);
328 return FALSE;
329 }
330
331 g_variant_get (response_body, "(^as)", &saveable_types);
332 if (saveable_types != NULL) {
333 saveable_type_list = g_strjoinv (", ", saveable_types);
334 rb_debug ("saveable types from metadata helper: %s", saveable_type_list);
335 g_free (saveable_type_list);
336 } else {
337 rb_debug ("unable to save metadata for any file types");
338 }
339 g_variant_unref (response_body);
340
341 return TRUE;
342 }
343
344 /**
345 * rb_metadata_reset:
346 * @md: a #RBMetaData
347 *
348 * Resets the state of the metadata interface. Call this before
349 * setting tags to be written to a file.
350 */
351 void
352 rb_metadata_reset (RBMetaData *md)
353 {
354 g_free (md->priv->media_type);
355 md->priv->media_type = NULL;
356
357 if (md->priv->metadata)
358 g_hash_table_destroy (md->priv->metadata);
359 md->priv->metadata = g_hash_table_new_full (g_direct_hash,
360 g_direct_equal,
361 NULL,
362 (GDestroyNotify)rb_value_free);
363 }
364
365 /**
366 * rb_metadata_load:
367 * @md: a #RBMetaData
368 * @uri: URI from which to load metadata
369 * @error: returns error information
370 *
371 * Reads metadata information from the specified URI.
372 * Once this has returned successfully (with *error == NULL),
373 * rb_metadata_get, rb_metadata_get_media_type, rb_metadata_has_missing_plugins,
374 * and rb_metadata_get_missing_plugins can usefully be called.
375 */
376 void
377 rb_metadata_load (RBMetaData *md,
378 const char *uri,
379 GError **error)
380 {
381 GVariant *response;
382 GError *fake_error = NULL;
383
384 if (error == NULL)
385 error = &fake_error;
386
387 rb_metadata_reset (md);
388 if (uri == NULL)
389 return;
390 g_static_mutex_lock (&conn_mutex);
391
392 start_metadata_service (error);
393
394 if (*error == NULL) {
395 rb_debug ("sending metadata load request: %s", uri);
396 response = g_dbus_connection_call_sync (dbus_connection,
397 RB_METADATA_DBUS_NAME,
398 RB_METADATA_DBUS_OBJECT_PATH,
399 RB_METADATA_DBUS_INTERFACE,
400 "load",
401 g_variant_new ("(s)", uri),
402 NULL, /* complicated return type */
403 G_DBUS_CALL_FLAGS_NONE,
404 RB_METADATA_DBUS_TIMEOUT,
405 NULL,
406 error);
407 }
408
409 if (*error == NULL) {
410 GVariantIter *metadata;
411 gboolean ok = FALSE;
412 int error_code;
413 char *error_string = NULL;
414
415 g_variant_get (response,
416 "(^as^asbbbsbisa{iv})",
417 &md->priv->missing_plugins,
418 &md->priv->plugin_descriptions,
419 &md->priv->has_audio,
420 &md->priv->has_video,
421 &md->priv->has_other_data,
422 &md->priv->media_type,
423 &ok,
424 &error_code,
425 &error_string,
426 &metadata);
427
428 if (ok) {
429 guint32 key;
430 GVariant *value;
431
432 while (g_variant_iter_next (metadata, "{iv}", &key, &value)) {
433 GValue *val = g_slice_new0 (GValue);
434
435 switch (rb_metadata_get_field_type (key)) {
436 case G_TYPE_STRING:
437 g_value_init (val, G_TYPE_STRING);
438 g_value_set_string (val, g_variant_get_string (value, NULL));
439 break;
440 case G_TYPE_ULONG:
441 g_value_init (val, G_TYPE_ULONG);
442 g_value_set_ulong (val, g_variant_get_uint32 (value));
443 break;
444 case G_TYPE_DOUBLE:
445 g_value_init (val, G_TYPE_DOUBLE);
446 g_value_set_double (val, g_variant_get_double (value));
447 break;
448 default:
449 g_assert_not_reached ();
450 break;
451 }
452 g_hash_table_insert (md->priv->metadata, GINT_TO_POINTER (key), val);
453 g_variant_unref (value);
454 }
455
456 } else {
457 g_set_error (error, RB_METADATA_ERROR,
458 error_code,
459 "%s", error_string);
460 }
461 g_variant_iter_free (metadata);
462
463 /* if we're missing some plugins, we'll need to make sure the
464 * metadata helper rereads the registry before the next load.
465 * the easiest way to do this is to kill it.
466 */
467 if (*error == NULL && g_strv_length (md->priv->missing_plugins) > 0) {
468 rb_debug ("missing plugins; killing metadata service to force registry reload");
469 kill_metadata_service ();
470 }
471 }
472 if (fake_error)
473 g_error_free (fake_error);
474
475 g_static_mutex_unlock (&conn_mutex);
476 }
477
478 /**
479 * rb_metadata_get_media_type:
480 * @md: a #RBMetaData
481 *
482 * Returns the type of the file from which metadata was read.
483 * This may look like a MIME type, but it isn't.
484 *
485 * Return value: media type string
486 */
487 const char *
488 rb_metadata_get_media_type (RBMetaData *md)
489 {
490 return md->priv->media_type;
491 }
492
493 /**
494 * rb_metadata_has_missing_plugins:
495 * @md: a #RBMetaData
496 *
497 * If the metadata reader could not decode the file it was asked to
498 * because one or more media framework plugins (specifically, for the
499 * existing implementations, GStreamer plugins) required are missing,
500 * this will return TRUE.
501 *
502 * Return value: TRUE if required plugins are missing
503 */
504 gboolean
505 rb_metadata_has_missing_plugins (RBMetaData *md)
506 {
507 return (md->priv->missing_plugins != NULL &&
508 g_strv_length (md->priv->missing_plugins) > 0);
509 }
510
511 /**
512 * rb_metadata_get_missing_plugins:
513 * @md: a #RBMetaData
514 * @missing_plugins: (out) (array zero-terminated=1): returns machine-readable
515 * missing plugin information
516 * @plugin_descriptions: (out) (array zero-terminated=1): returns human-readable
517 * missing plugin descriptions
518 *
519 * This function returns the information used to request automatic
520 * installation of media framework plugins required to decode the target URI.
521 * Use g_strfreev() to free the returned information arrays.
522 *
523 * Return value: TRUE if missing plugin information was returned
524 */
525 gboolean
526 rb_metadata_get_missing_plugins (RBMetaData *md,
527 char ***missing_plugins,
528 char ***plugin_descriptions)
529 {
530 if (rb_metadata_has_missing_plugins (md) == FALSE) {
531 return FALSE;
532 }
533
534 *missing_plugins = g_strdupv (md->priv->missing_plugins);
535 *plugin_descriptions = g_strdupv (md->priv->plugin_descriptions);
536 return TRUE;
537 }
538
539
540 /**
541 * rb_metadata_get:
542 * @md: a #RBMetaData
543 * @field: the #RBMetaDataField to retrieve
544 * @val: (out caller-allocates) (transfer full): returns the field value
545 *
546 * Retrieves the value of a metadata field extracted from the target URI.
547 * If the target URI contained no value for the field, returns FALSE.
548 *
549 * Return value: TRUE if a value was returned
550 */
551 gboolean
552 rb_metadata_get (RBMetaData *md, RBMetaDataField field, GValue *ret)
553 {
554 GValue *val;
555 if (!md->priv->metadata)
556 return FALSE;
557
558 if ((val = g_hash_table_lookup (md->priv->metadata,
559 GINT_TO_POINTER (field)))) {
560 g_value_init (ret, G_VALUE_TYPE (val));
561 g_value_copy (val, ret);
562 return TRUE;
563 }
564 return FALSE;
565 }
566
567 /**
568 * rb_metadata_set:
569 * @md: a #RBMetaData
570 * @field: the #RBMetaDataField to set
571 * @val: the value to set
572 *
573 * Sets a metadata field value. The value is only stored inside the
574 * #RBMetaData object until rb_metadata_save is called.
575 *
576 * Return value: TRUE if the field is valid
577 */
578 gboolean
579 rb_metadata_set (RBMetaData *md, RBMetaDataField field,
580 const GValue *val)
581 {
582 GValue *newval;
583 GType type;
584
585 type = rb_metadata_get_field_type (field);
586 g_return_val_if_fail (type == G_VALUE_TYPE (val), FALSE);
587
588 newval = g_slice_new0 (GValue);
589 g_value_init (newval, type);
590 g_value_copy (val, newval);
591
592 g_hash_table_insert (md->priv->metadata, GINT_TO_POINTER (field),
593 newval);
594 return TRUE;
595 }
596
597 /**
598 * rb_metadata_can_save:
599 * @md: a #RBMetaData
600 * @media_type: the media type string to check
601 *
602 * Checks if the metadata writer is capable of updating file metadata
603 * for a given media type.
604 *
605 * Return value: TRUE if the file metadata for the given media type can be updated
606 */
607 gboolean
608 rb_metadata_can_save (RBMetaData *md, const char *media_type)
609 {
610 GError *error = NULL;
611 gboolean result = FALSE;
612 int i = 0;
613
614 g_static_mutex_lock (&conn_mutex);
615
616 if (saveable_types == NULL) {
617 if (start_metadata_service (&error) == FALSE) {
618 g_error_free (error);
619 return FALSE;
620 }
621 }
622
623 if (saveable_types != NULL) {
624 for (i = 0; saveable_types[i] != NULL; i++) {
625 if (g_str_equal (media_type, saveable_types[i])) {
626 result = TRUE;
627 break;
628 }
629 }
630 }
631
632 g_static_mutex_unlock (&conn_mutex);
633 return result;
634 }
635
636 /**
637 * rb_metadata_get_saveable_types:
638 * @md: a #RBMetaData
639 *
640 * Constructs a list of the media types for which the metadata backend
641 * implements tag saving.
642 *
643 * Return value: (transfer full) (array zero-terminated=1): a NULL-terminated
644 * array of media type strings. Use g_strfreev to free it.
645 */
646 char **
647 rb_metadata_get_saveable_types (RBMetaData *md)
648 {
649 return g_strdupv (saveable_types);
650 }
651
652 /**
653 * rb_metadata_save:
654 * @md: a #RBMetaData
655 * @uri: the target URI
656 * @error: returns error information
657 *
658 * Saves all metadata changes made with rb_metadata_set to the
659 * target URI.
660 */
661 void
662 rb_metadata_save (RBMetaData *md, const char *uri, GError **error)
663 {
664 GVariant *response;
665 GError *fake_error = NULL;
666
667 if (error == NULL)
668 error = &fake_error;
669
670 g_static_mutex_lock (&conn_mutex);
671
672 start_metadata_service (error);
673
674 if (*error == NULL) {
675 response = g_dbus_connection_call_sync (dbus_connection,
676 RB_METADATA_DBUS_NAME,
677 RB_METADATA_DBUS_OBJECT_PATH,
678 RB_METADATA_DBUS_INTERFACE,
679 "save",
680 g_variant_new ("(sa{iv})",
681 uri,
682 rb_metadata_dbus_get_variant_builder (md)),
683 NULL,
684 G_DBUS_CALL_FLAGS_NONE,
685 RB_METADATA_SAVE_DBUS_TIMEOUT,
686 NULL,
687 error);
688 }
689
690 if (*error == NULL) {
691 gboolean ok = TRUE;
692 int error_code;
693 const char *error_message;
694
695 g_variant_get (response, "(bis)", &ok, &error_code, &error_message);
696 if (ok == FALSE) {
697 g_set_error (error, RB_METADATA_ERROR,
698 error_code,
699 "%s", error_message);
700 }
701
702 g_variant_unref (response);
703 }
704
705 if (fake_error)
706 g_error_free (fake_error);
707
708 g_static_mutex_unlock (&conn_mutex);
709 }
710
711 gboolean
712 rb_metadata_has_audio (RBMetaData *md)
713 {
714 return md->priv->has_audio;
715 }
716
717 gboolean
718 rb_metadata_has_video (RBMetaData *md)
719 {
720 return md->priv->has_video;
721 }
722
723 gboolean
724 rb_metadata_has_other_data (RBMetaData *md)
725 {
726 return md->priv->has_other_data;
727 }