2006-10-27 James Livingston <doclivingston@gmail.com>
[rhythmbox.git] / metadata / rb-metadata-dbus-client.c
blob9345c02567e1fecdedf4cf29d8bc4b36c9b9c51d
1 /*
2 * Copyright (C) 2006 Jonathan Matthew <jonathan@kaolin.hn.org>
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.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21 * Client for out-of-process metadata reader communicating via D-BUS.
23 * How this works:
24 * - spawn rb-metadata process, with pipes
25 * - child process sets up its dbus server or whatever
26 * - if successful, child writes dbus server address to stdout; otherwise, dies.
27 * - parent opens dbus connection
29 * For each request, the parent checks if the dbus connection is still alive,
30 * and pings the child to see if it's still responding. If the child has
31 * exited or is not responding, the parent starts a new metadata helper as
32 * described above.
34 * The child process exits after a certain period of inactivity (30s
35 * currently), so the ping message serves two purposes - it checks that the
36 * child is still capable of handling messages, and it ensures the child
37 * doesn't time out between when we check the child is still running and when
38 * we actually send it the request.
41 #include <config.h>
43 #include "rb-metadata.h"
44 #include "rb-metadata-dbus.h"
45 #include "rb-debug.h"
46 #include "rb-util.h"
48 #include <dbus/dbus.h>
49 #include <dbus/dbus-glib.h>
50 #include <dbus/dbus-glib-lowlevel.h>
51 #include <glib/gi18n.h>
53 #include <unistd.h>
54 #include <sys/types.h>
55 #include <sys/signal.h>
56 #include <sys/wait.h>
57 #include <string.h>
58 #include <stdlib.h>
60 static void rb_metadata_class_init (RBMetaDataClass *klass);
61 static void rb_metadata_init (RBMetaData *md);
62 static void rb_metadata_finalize (GObject *object);
64 static gboolean tried_env_address = FALSE;
65 static DBusConnection *dbus_connection = NULL;
66 static GPid metadata_child = 0;
67 static GMainContext *main_context = NULL;
68 static GStaticMutex conn_mutex = G_STATIC_MUTEX_INIT;
70 struct RBMetaDataPrivate
72 char *uri;
73 char *mimetype;
74 GHashTable *metadata;
77 G_DEFINE_TYPE (RBMetaData, rb_metadata, G_TYPE_OBJECT)
79 static void
80 rb_metadata_class_init (RBMetaDataClass *klass)
82 GObjectClass *object_class = G_OBJECT_CLASS (klass);
83 object_class->finalize = rb_metadata_finalize;
85 g_type_class_add_private (object_class, sizeof (RBMetaDataPrivate));
87 main_context = g_main_context_new (); /* maybe not needed? */
90 static void
91 rb_metadata_init (RBMetaData *md)
93 md->priv = G_TYPE_INSTANCE_GET_PRIVATE (md, RB_TYPE_METADATA, RBMetaDataPrivate);
96 static void
97 rb_metadata_finalize (GObject *object)
99 RBMetaData *md;
101 md = RB_METADATA (object);
102 g_free (md->priv->uri);
103 g_free (md->priv->mimetype);
104 if (md->priv->metadata)
105 g_hash_table_destroy (md->priv->metadata);
107 G_OBJECT_CLASS (rb_metadata_parent_class)->finalize (object);
110 RBMetaData *
111 rb_metadata_new (void)
113 return RB_METADATA (g_object_new (RB_TYPE_METADATA, NULL));
116 static void
117 kill_metadata_service (void)
119 if (dbus_connection) {
120 if (dbus_connection_get_is_connected (dbus_connection)) {
121 rb_debug ("closing dbus connection");
122 #ifdef WITH_OLD_DBUS
123 dbus_connection_disconnect (dbus_connection);
124 #else
125 dbus_connection_close (dbus_connection);
126 #endif
127 } else {
128 rb_debug ("dbus connection already closed");
130 dbus_connection_unref (dbus_connection);
131 dbus_connection = NULL;
134 if (metadata_child) {
135 rb_debug ("killing child process");
136 kill (metadata_child, SIGINT);
137 g_spawn_close_pid (metadata_child);
138 metadata_child = 0;
142 static gboolean
143 ping_metadata_service (GError **error)
145 DBusMessage *message, *response;
146 DBusError dbus_error = {0,};
148 if (!dbus_connection_get_is_connected (dbus_connection))
149 return FALSE;
151 message = dbus_message_new_method_call (RB_METADATA_DBUS_NAME,
152 RB_METADATA_DBUS_OBJECT_PATH,
153 RB_METADATA_DBUS_INTERFACE,
154 "ping");
155 if (!message) {
156 return FALSE;
158 response = dbus_connection_send_with_reply_and_block (dbus_connection,
159 message,
160 RB_METADATA_DBUS_TIMEOUT,
161 &dbus_error);
162 dbus_message_unref (message);
163 if (dbus_error_is_set (&dbus_error)) {
164 /* ignore 'no reply': just means the service is dead */
165 if (strcmp (dbus_error.name, DBUS_ERROR_NO_REPLY)) {
166 dbus_set_g_error (error, &dbus_error);
168 dbus_error_free (&dbus_error);
169 return FALSE;
171 dbus_message_unref (response);
172 return TRUE;
175 static gboolean
176 start_metadata_service (GError **error)
179 * Normally, we find the metadata helper in the libexec dir,
180 * but when --enable-uninstalled-build is specified, we look
181 * in the directory it's built in.
183 char *argv[] = {
184 #ifdef METADATA_UNINSTALLED_DIR
185 METADATA_UNINSTALLED_DIR "/rhythmbox-metadata",
186 #else
187 LIBEXEC_DIR "/rhythmbox-metadata",
188 #endif
189 "unix:tmpdir=/tmp", NULL
191 DBusError dbus_error = {0,};
192 GIOChannel *stdout_channel;
193 GIOStatus status;
194 gchar *dbus_address = NULL;
196 if (dbus_connection) {
197 if (ping_metadata_service (error))
198 return TRUE;
200 /* Metadata service is broken. Kill it, and if we haven't run
201 * into any errors yet, we can try to restart it.
203 kill_metadata_service ();
205 if (*error)
206 return FALSE;
209 if (!tried_env_address) {
210 const char *addr = g_getenv ("RB_DBUS_METADATA_ADDRESS");
211 tried_env_address = TRUE;
212 if (addr) {
213 rb_debug ("trying metadata service address %s (from environment)", addr);
214 dbus_address = g_strdup (addr);
215 metadata_child = 0;
219 if (dbus_address == NULL) {
220 gint metadata_stdout;
222 if (!g_spawn_async_with_pipes (NULL,
223 argv,
224 NULL,
226 NULL, NULL,
227 &metadata_child,
228 NULL,
229 &metadata_stdout,
230 NULL,
231 error)) {
232 return FALSE;
235 stdout_channel = g_io_channel_unix_new (metadata_stdout);
236 status = g_io_channel_read_line (stdout_channel, &dbus_address, NULL, NULL, error);
237 g_io_channel_unref (stdout_channel);
238 if (status != G_IO_STATUS_NORMAL) {
239 kill_metadata_service ();
240 return FALSE;
243 g_strchomp (dbus_address);
244 rb_debug ("Got metadata helper D-BUS address %s", dbus_address);
247 dbus_connection = dbus_connection_open_private (dbus_address, &dbus_error);
248 g_free (dbus_address);
249 if (!dbus_connection) {
250 kill_metadata_service ();
252 dbus_set_g_error (error, &dbus_error);
253 dbus_error_free (&dbus_error);
254 return FALSE;
256 dbus_connection_set_exit_on_disconnect (dbus_connection, FALSE);
258 dbus_connection_setup_with_g_main (dbus_connection, main_context);
260 rb_debug ("Metadata process %d started", metadata_child);
261 return TRUE;
264 static void
265 handle_dbus_error (RBMetaData *md, DBusError *dbus_error, GError **error)
268 * If the error is 'no reply within the specified time',
269 * then we assume that either the metadata process died, or
270 * it's stuck in a loop and needs to be killed.
272 if (strcmp (dbus_error->name, DBUS_ERROR_NO_REPLY) == 0) {
273 kill_metadata_service ();
275 g_set_error (error,
276 RB_METADATA_ERROR,
277 RB_METADATA_ERROR_INTERNAL,
278 _("Internal GStreamer problem; file a bug"));
279 } else {
280 dbus_set_g_error (error, dbus_error);
281 dbus_error_free (dbus_error);
285 static void
286 read_error_from_message (RBMetaData *md, DBusMessageIter *iter, GError **error)
288 guint32 error_code;
289 gchar *error_message;
291 if (!rb_metadata_dbus_get_uint32 (iter, &error_code) ||
292 !rb_metadata_dbus_get_string (iter, &error_message)) {
293 g_set_error (error,
294 RB_METADATA_ERROR,
295 RB_METADATA_ERROR_INTERNAL,
296 _("D-BUS communication error"));
297 return;
300 g_set_error (error, RB_METADATA_ERROR,
301 error_code,
302 "%s", error_message);
303 g_free (error_message);
306 void
307 rb_metadata_load (RBMetaData *md,
308 const char *uri,
309 GError **error)
311 DBusMessage *message = NULL;
312 DBusMessage *response = NULL;
313 DBusMessageIter iter;
314 DBusError dbus_error = {0,};
315 gboolean ok;
316 GError *fake_error = NULL;
318 if (error == NULL)
319 error = &fake_error;
321 g_free (md->priv->mimetype);
322 md->priv->mimetype = NULL;
324 g_free (md->priv->uri);
325 md->priv->uri = g_strdup (uri);
326 if (uri == NULL)
327 return;
329 if (md->priv->metadata)
330 g_hash_table_destroy (md->priv->metadata);
331 md->priv->metadata = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)rb_value_free);
333 g_static_mutex_lock (&conn_mutex);
335 start_metadata_service (error);
337 if (*error == NULL) {
338 message = dbus_message_new_method_call (RB_METADATA_DBUS_NAME,
339 RB_METADATA_DBUS_OBJECT_PATH,
340 RB_METADATA_DBUS_INTERFACE,
341 "load");
342 if (!message) {
343 g_set_error (error,
344 RB_METADATA_ERROR,
345 RB_METADATA_ERROR_INTERNAL,
346 _("D-BUS communication error"));
347 } else if (!dbus_message_append_args (message, DBUS_TYPE_STRING, &uri, DBUS_TYPE_INVALID)) {
348 g_set_error (error,
349 RB_METADATA_ERROR,
350 RB_METADATA_ERROR_INTERNAL,
351 _("D-BUS communication error"));
355 if (*error == NULL) {
356 rb_debug ("sending metadata load request");
357 response = dbus_connection_send_with_reply_and_block (dbus_connection,
358 message,
359 RB_METADATA_DBUS_TIMEOUT,
360 &dbus_error);
362 if (!response)
363 handle_dbus_error (md, &dbus_error, error);
366 if (*error == NULL) {
367 if (!dbus_message_iter_init (response, &iter)) {
368 g_set_error (error,
369 RB_METADATA_ERROR,
370 RB_METADATA_ERROR_INTERNAL,
371 _("D-BUS communication error"));
372 rb_debug ("couldn't read response message");
376 if (*error == NULL) {
377 if (!rb_metadata_dbus_get_boolean (&iter, &ok)) {
378 g_set_error (error,
379 RB_METADATA_ERROR,
380 RB_METADATA_ERROR_INTERNAL,
381 _("D-BUS communication error"));
382 rb_debug ("couldn't get success flag from response message");
383 } else if (ok) {
384 /* get mime type */
385 if (!rb_metadata_dbus_get_string (&iter, &md->priv->mimetype)) {
386 g_set_error (error,
387 RB_METADATA_ERROR,
388 RB_METADATA_ERROR_INTERNAL,
389 _("D-BUS communication error"));
390 } else {
391 /* get metadata */
392 rb_debug ("got mimetype: %s", md->priv->mimetype);
393 rb_metadata_dbus_read_from_message (md, md->priv->metadata, &iter);
395 } else {
396 read_error_from_message (md, &iter, error);
400 if (message)
401 dbus_message_unref (message);
402 if (response)
403 dbus_message_unref (response);
404 if (fake_error)
405 g_error_free (fake_error);
407 g_static_mutex_unlock (&conn_mutex);
410 const char *
411 rb_metadata_get_mime (RBMetaData *md)
413 return md->priv->mimetype;
416 gboolean
417 rb_metadata_get (RBMetaData *md, RBMetaDataField field,
418 GValue *ret)
420 GValue *val;
421 if (!md->priv->metadata)
422 return FALSE;
424 if ((val = g_hash_table_lookup (md->priv->metadata,
425 GINT_TO_POINTER (field)))) {
426 g_value_init (ret, G_VALUE_TYPE (val));
427 g_value_copy (val, ret);
428 return TRUE;
430 return FALSE;
433 gboolean
434 rb_metadata_set (RBMetaData *md, RBMetaDataField field,
435 const GValue *val)
437 GValue *newval;
438 GType type;
440 type = rb_metadata_get_field_type (field);
441 g_return_val_if_fail (type == G_VALUE_TYPE (val), FALSE);
443 newval = g_new0 (GValue, 1);
444 g_value_init (newval, type);
445 g_value_copy (val, newval);
447 g_hash_table_insert (md->priv->metadata, GINT_TO_POINTER (field),
448 newval);
449 return TRUE;
452 gboolean
453 rb_metadata_can_save (RBMetaData *md, const char *mimetype)
455 GError *error = NULL;
456 DBusMessage *message = NULL;
457 DBusMessage *response = NULL;
458 gboolean can_save = FALSE;
459 DBusError dbus_error = {0,};
460 DBusMessageIter iter;
461 gboolean ok = TRUE;
463 g_static_mutex_lock (&conn_mutex);
465 if (start_metadata_service (&error) == FALSE) {
466 g_error_free (error);
467 ok = FALSE;
470 if (ok) {
471 message = dbus_message_new_method_call (RB_METADATA_DBUS_NAME,
472 RB_METADATA_DBUS_OBJECT_PATH,
473 RB_METADATA_DBUS_INTERFACE,
474 "canSave");
475 if (!message) {
476 ok = FALSE;
477 } else if (!dbus_message_append_args (message, DBUS_TYPE_STRING, &mimetype, DBUS_TYPE_INVALID)) {
478 ok = FALSE;
482 if (ok) {
483 response = dbus_connection_send_with_reply_and_block (dbus_connection,
484 message,
485 RB_METADATA_DBUS_TIMEOUT,
486 &dbus_error);
487 if (!response) {
488 dbus_error_free (&dbus_error);
489 ok = FALSE;
490 } else if (dbus_message_iter_init (response, &iter)) {
491 rb_metadata_dbus_get_boolean (&iter, &can_save);
495 if (message)
496 dbus_message_unref (message);
497 if (response)
498 dbus_message_unref (response);
499 g_static_mutex_unlock (&conn_mutex);
501 return can_save;
504 void
505 rb_metadata_save (RBMetaData *md, GError **error)
507 GError *fake_error = NULL;
508 DBusMessage *message = NULL;
509 DBusMessage *response = NULL;
510 DBusError dbus_error = {0,};
511 DBusMessageIter iter;
513 if (error == NULL)
514 error = &fake_error;
516 g_static_mutex_lock (&conn_mutex);
518 start_metadata_service (error);
520 if (*error == NULL) {
521 message = dbus_message_new_method_call (RB_METADATA_DBUS_NAME,
522 RB_METADATA_DBUS_OBJECT_PATH,
523 RB_METADATA_DBUS_INTERFACE,
524 "save");
525 if (!message) {
526 g_set_error (error,
527 RB_METADATA_ERROR,
528 RB_METADATA_ERROR_INTERNAL,
529 _("D-BUS communication error"));
533 if (*error == NULL) {
534 dbus_message_iter_init_append (message, &iter);
535 if (!rb_metadata_dbus_add_to_message (md, &iter)) {
536 g_set_error (error,
537 RB_METADATA_ERROR,
538 RB_METADATA_ERROR_INTERNAL,
539 _("D-BUS communication error"));
543 if (*error == NULL) {
544 response = dbus_connection_send_with_reply_and_block (dbus_connection,
545 message,
546 RB_METADATA_DBUS_TIMEOUT,
547 &dbus_error);
548 if (!response) {
549 handle_dbus_error (md, &dbus_error, error);
550 } else if (dbus_message_iter_init (response, &iter)) {
551 /* if there's any return data at all, it'll be an error */
552 read_error_from_message (md, &iter, error);
556 if (message)
557 dbus_message_unref (message);
558 if (response)
559 dbus_message_unref (response);
560 if (fake_error)
561 g_error_free (fake_error);
563 g_static_mutex_unlock (&conn_mutex);