upnp: s/url/URL/ in GetAll() D-Bus call
[profile/ivi/pulseaudio-panda.git] / src / modules / module-rygel-media-server.c
1 /***
2   This file is part of PulseAudio.
3
4   Copyright 2005-2009 Lennart Poettering
5
6   PulseAudio is free software; you can redistribute it and/or modify
7   it under the terms of the GNU Lesser General Public License as published
8   by the Free Software Foundation; either version 2.1 of the License,
9   or (at your option) any later version.
10
11   PulseAudio is distributed in the hope that it will be useful, but
12   WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14   General Public License for more details.
15
16   You should have received a copy of the GNU Lesser General Public License
17   along with PulseAudio; if not, write to the Free Software
18   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19   USA.
20 ***/
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30
31 #include <pulse/xmalloc.h>
32 #include <pulse/util.h>
33 #include <pulse/i18n.h>
34 #include <pulse/utf8.h>
35
36 #include <pulsecore/sink.h>
37 #include <pulsecore/source.h>
38 #include <pulsecore/core-util.h>
39 #include <pulsecore/log.h>
40 #include <pulsecore/modargs.h>
41 #include <pulsecore/dbus-shared.h>
42 #include <pulsecore/endianmacros.h>
43 #include <pulsecore/namereg.h>
44 #include <pulsecore/mime-type.h>
45 #include <pulsecore/strbuf.h>
46 #include <pulsecore/protocol-http.h>
47 #include <pulsecore/parseaddr.h>
48
49 #include "module-rygel-media-server-symdef.h"
50
51 PA_MODULE_AUTHOR("Lennart Poettering");
52 PA_MODULE_DESCRIPTION("UPnP MediaServer Plugin for Rygel");
53 PA_MODULE_VERSION(PACKAGE_VERSION);
54 PA_MODULE_LOAD_ONCE(TRUE);
55 PA_MODULE_USAGE(
56         "display_name=<UPnP Media Server name>");
57
58 /* This implements http://live.gnome.org/Rygel/MediaServerSpec */
59
60 #define SERVICE_NAME "org.Rygel.MediaServer1.PulseAudio"
61
62 #define OBJECT_ROOT "/org/Rygel/MediaServer1/PulseAudio"
63 #define OBJECT_SINKS "/org/Rygel/MediaServer1/PulseAudio/Sinks"
64 #define OBJECT_SOURCES "/org/Rygel/MediaServer1/PulseAudio/Sources"
65
66 #define CONTAINER_INTROSPECT_XML_PREFIX                                 \
67     DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                           \
68     "<node>"                                                            \
69     " <!-- If you are looking for documentation make sure to check out" \
70     "      http://live.gnome.org/Rygel/MediaServerSpec -->"             \
71     " <interface name=\"org.Rygel.MediaContainer1\">"                   \
72     "  <signal name=\"Updated\">"                                       \
73     "   <arg name=\"path\" type=\"o\"/>"                                \
74     "  </signal>"                                                       \
75     "  <property name=\"Items\" type=\"ao\" access=\"read\"/>"          \
76     "  <property name=\"ItemCount\" type=\"u\" access=\"read\"/>"       \
77     "  <property name=\"Containers\" type=\"ao\" access=\"read\"/>"     \
78     "  <property name=\"ContainerCount\" type=\"u\" access=\"read\"/>"  \
79     " </interface>"                                                     \
80     " <interface name=\"org.Rygel.MediaObject1\">"                      \
81     "  <property name=\"Parent\" type=\"s\" access=\"read\"/>"          \
82     "  <property name=\"DisplayName\" type=\"s\" access=\"read\"/>"     \
83     " </interface>"                                                     \
84     " <interface name=\"org.freedesktop.DBus.Properties\">"             \
85     "  <method name=\"Get\">"                                           \
86     "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>"          \
87     "   <arg name=\"property\" direction=\"in\" type=\"s\"/>"           \
88     "   <arg name=\"value\" direction=\"out\" type=\"v\"/>"             \
89     "  </method>"                                                       \
90     "  <method name=\"GetAll\">"                                        \
91     "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>"          \
92     "   <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>"    \
93     "  </method>"                                                       \
94     " </interface>"                                                     \
95     " <interface name=\"org.freedesktop.DBus.Introspectable\">"         \
96     "  <method name=\"Introspect\">"                                    \
97     "   <arg name=\"data\" type=\"s\" direction=\"out\"/>"              \
98     "  </method>"                                                       \
99     " </interface>"
100
101 #define CONTAINER_INTROSPECT_XML_POSTFIX                                \
102     "</node>"
103
104 #define ROOT_INTROSPECT_XML                                             \
105     CONTAINER_INTROSPECT_XML_PREFIX                                     \
106     "<node name=\"Sinks\"/>"                                            \
107     "<node name=\"Sources\"/>"                                          \
108     CONTAINER_INTROSPECT_XML_POSTFIX
109
110 #define ITEM_INTROSPECT_XML                                             \
111     DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                           \
112     "<node>"                                                            \
113     " <!-- If you are looking for documentation make sure to check out" \
114     "      http://live.gnome.org/Rygel/MediaProviderSpec -->"           \
115     " <interface name=\"org.Rygel.MediaItem1\">"                        \
116     "  <property name=\"URLs\" type=\"as\" access=\"read\"/>"           \
117     "  <property name=\"MIMEType\" type=\"s\" access=\"read\"/>"        \
118     "  <property name=\"Type\" type=\"s\" access=\"read\"/>"            \
119     " </interface>"                                                     \
120     " <interface name=\"org.Rygel.MediaObject1\">"                      \
121     "  <property name=\"Parent\" type=\"s\" access=\"read\"/>"          \
122     "  <property name=\"DisplayName\" type=\"s\" access=\"read\"/>"     \
123     " </interface>"                                                     \
124     " <interface name=\"org.freedesktop.DBus.Properties\">"             \
125     "  <method name=\"Get\">"                                           \
126     "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>"          \
127     "   <arg name=\"property\" direction=\"in\" type=\"s\"/>"           \
128     "   <arg name=\"value\" direction=\"out\" type=\"v\"/>"             \
129     "  </method>"                                                       \
130     "  <method name=\"GetAll\">"                                        \
131     "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>"          \
132     "   <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>"    \
133     "  </method>"                                                       \
134     " </interface>"                                                     \
135     " <interface name=\"org.freedesktop.DBus.Introspectable\">"         \
136     "  <method name=\"Introspect\">"                                    \
137     "   <arg name=\"data\" type=\"s\" direction=\"out\"/>"              \
138     "  </method>"                                                       \
139     " </interface>"                                                     \
140     "</node>"
141
142
143 static const char* const valid_modargs[] = {
144     "display_name",
145     NULL
146 };
147
148 struct userdata {
149     pa_core *core;
150     pa_module *module;
151
152     pa_dbus_connection *bus;
153     pa_bool_t got_name:1;
154
155     char *display_name;
156
157     pa_hook_slot *source_new_slot, *source_unlink_slot;
158
159     pa_http_protocol *http;
160 };
161
162 static void send_signal(struct userdata *u, pa_source *s) {
163     DBusMessage *m;
164     const char *parent;
165
166     pa_assert(u);
167     pa_source_assert_ref(s);
168
169     if (u->core->state == PA_CORE_SHUTDOWN)
170         return;
171
172     if (s->monitor_of)
173         parent = OBJECT_SINKS;
174     else
175         parent = OBJECT_SOURCES;
176
177     pa_assert_se(m = dbus_message_new_signal(parent, "org.Rygel.MediaContainer1", "Updated"));
178     pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->bus), m, NULL));
179
180     dbus_message_unref(m);
181 }
182
183 static pa_hook_result_t source_new_or_unlink_cb(pa_core *c, pa_source *s, struct userdata *u) {
184     pa_assert(c);
185     pa_source_assert_ref(s);
186
187     send_signal(u, s);
188
189     return PA_HOOK_OK;
190 }
191
192 static pa_bool_t message_is_property_get(DBusMessage *m, const char *interface, const char *property) {
193     const char *i, *p;
194     DBusError error;
195
196     dbus_error_init(&error);
197
198     pa_assert(m);
199
200     if (!dbus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Get"))
201         return FALSE;
202
203     if (!dbus_message_get_args(m, &error, DBUS_TYPE_STRING, &i, DBUS_TYPE_STRING, &p, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) {
204         dbus_error_free(&error);
205         return FALSE;
206     }
207
208     return pa_streq(i, interface) && pa_streq(p, property);
209 }
210
211 static pa_bool_t message_is_property_get_all(DBusMessage *m, const char *interface) {
212     const char *i;
213     DBusError error;
214
215     dbus_error_init(&error);
216
217     pa_assert(m);
218
219     if (!dbus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "GetAll"))
220         return FALSE;
221
222     if (!dbus_message_get_args(m, &error, DBUS_TYPE_STRING, &i, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) {
223         dbus_error_free(&error);
224         return FALSE;
225     }
226
227     return pa_streq(i, interface);
228 }
229
230 static void append_variant_object_array(DBusMessage *m, DBusMessageIter *iter, const char *path[], unsigned n) {
231     DBusMessageIter _iter, variant, array;
232     unsigned c;
233
234     pa_assert(m);
235     pa_assert(path);
236
237     if (!iter) {
238         dbus_message_iter_init_append(m, &_iter);
239         iter = &_iter;
240     }
241
242     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "ao", &variant));
243     pa_assert_se(dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, "o", &array));
244
245     for (c = 0; c < n; c++)
246         pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_OBJECT_PATH, path + c));
247
248     pa_assert_se(dbus_message_iter_close_container(&variant, &array));
249     pa_assert_se(dbus_message_iter_close_container(iter, &variant));
250 }
251
252 static void append_variant_string(DBusMessage *m, DBusMessageIter *iter, const char *s) {
253     DBusMessageIter _iter, sub;
254
255     pa_assert(m);
256     pa_assert(s);
257
258     if (!iter) {
259         dbus_message_iter_init_append(m, &_iter);
260         iter = &_iter;
261     }
262
263     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "s", &sub));
264     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &s));
265     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
266 }
267
268 static void append_variant_object(DBusMessage *m, DBusMessageIter *iter, const char *s) {
269     DBusMessageIter _iter, sub;
270
271     pa_assert(m);
272     pa_assert(s);
273
274     if (!iter) {
275         dbus_message_iter_init_append(m, &_iter);
276         iter = &_iter;
277     }
278
279     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "o", &sub));
280     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &s));
281     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
282 }
283
284 static void append_variant_unsigned(DBusMessage *m, DBusMessageIter *iter, unsigned u) {
285     DBusMessageIter _iter, sub;
286
287     pa_assert(m);
288
289     if (!iter) {
290         dbus_message_iter_init_append(m, &_iter);
291         iter = &_iter;
292     }
293
294     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "u", &sub));
295     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &u));
296     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
297 }
298
299 static void append_property_dict_entry_object_array(DBusMessage *m, DBusMessageIter *iter, const char *name, const char *path[], unsigned n) {
300     DBusMessageIter sub;
301
302     pa_assert(iter);
303
304     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub));
305     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name));
306     append_variant_object_array(m, &sub, path, n);
307     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
308 }
309
310 static void append_property_dict_entry_string(DBusMessage *m, DBusMessageIter *iter, const char *name, const char *value) {
311     DBusMessageIter sub;
312
313     pa_assert(iter);
314
315     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub));
316     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name));
317     append_variant_string(m, &sub, value);
318     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
319 }
320
321 static void append_property_dict_entry_object(DBusMessage *m, DBusMessageIter *iter, const char *name, const char *value) {
322     DBusMessageIter sub;
323
324     pa_assert(iter);
325
326     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub));
327     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name));
328     append_variant_object(m, &sub, value);
329     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
330 }
331
332 static void append_property_dict_entry_unsigned(DBusMessage *m, DBusMessageIter *iter, const char *name, unsigned u) {
333     DBusMessageIter sub;
334
335     pa_assert(iter);
336
337     pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub));
338     pa_assert_se(dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name));
339     append_variant_unsigned(m, &sub, u);
340     pa_assert_se(dbus_message_iter_close_container(iter, &sub));
341 }
342
343 static const char *array_root_containers[] = { OBJECT_SINKS, OBJECT_SOURCES };
344 static const char *array_no_children[] = { };
345
346 static DBusHandlerResult root_handler(DBusConnection *c, DBusMessage *m, void *userdata) {
347     struct userdata *u = userdata;
348     DBusMessage *r = NULL;
349
350     pa_assert(u);
351
352     if (message_is_property_get(m, "org.Rygel.MediaContainer1", "Containers")) {
353         pa_assert_se(r = dbus_message_new_method_return(m));
354         append_variant_object_array(r, NULL, (const char**) array_root_containers, PA_ELEMENTSOF(array_root_containers));
355
356     } else if (message_is_property_get(m, "org.Rygel.MediaContainer1", "ContainerCount")) {
357         pa_assert_se(r = dbus_message_new_method_return(m));
358         append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_root_containers));
359
360     } else if (message_is_property_get(m, "org.Rygel.MediaContainer1", "Items")) {
361         pa_assert_se(r = dbus_message_new_method_return(m));
362         append_variant_object_array(r, NULL, array_no_children, PA_ELEMENTSOF(array_no_children));
363
364     } else if (message_is_property_get(m, "org.Rygel.MediaContainer1", "ItemCount")) {
365         pa_assert_se(r = dbus_message_new_method_return(m));
366         append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_no_children));
367
368     } else if (message_is_property_get_all(m, "org.Rygel.MediaContainer1")) {
369         DBusMessageIter iter, sub;
370
371         pa_assert_se(r = dbus_message_new_method_return(m));
372         dbus_message_iter_init_append(r, &iter);
373
374         pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
375         append_property_dict_entry_object_array(r, &sub, "Containers", array_root_containers, PA_ELEMENTSOF(array_root_containers));
376         append_property_dict_entry_unsigned(r, &sub, "ContainerCount", PA_ELEMENTSOF(array_root_containers));
377         append_property_dict_entry_object_array(r, &sub, "Items", array_no_children, PA_ELEMENTSOF(array_no_children));
378         append_property_dict_entry_unsigned(r, &sub, "ItemCount", PA_ELEMENTSOF(array_no_children));
379         pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
380
381     } else if (message_is_property_get(m, "org.Rygel.MediaObject1", "Parent")) {
382         pa_assert_se(r = dbus_message_new_method_return(m));
383         append_variant_object(r, NULL, OBJECT_ROOT);
384
385     } else if (message_is_property_get(m, "org.Rygel.MediaObject1", "DisplayName")) {
386         pa_assert_se(r = dbus_message_new_method_return(m));
387         append_variant_string(r, NULL, u->display_name);
388
389     } else if (message_is_property_get_all(m, "org.Rygel.MediaObject1")) {
390         DBusMessageIter iter, sub;
391
392         pa_assert_se(r = dbus_message_new_method_return(m));
393         dbus_message_iter_init_append(r, &iter);
394
395         pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
396         append_property_dict_entry_string(r, &sub, "DisplayName", u->display_name);
397         pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
398
399     } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
400         const char *xml = ROOT_INTROSPECT_XML;
401
402         pa_assert_se(r = dbus_message_new_method_return(m));
403         pa_assert_se(dbus_message_append_args(
404                              r,
405                              DBUS_TYPE_STRING, &xml,
406                              DBUS_TYPE_INVALID));
407
408     } else
409         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
410
411     if (r) {
412         pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->bus), r, NULL));
413         dbus_message_unref(r);
414     }
415
416     return DBUS_HANDLER_RESULT_HANDLED;
417 }
418
419 static char *compute_url(struct userdata *u, const char *name) {
420     pa_strlist *i;
421
422     pa_assert(u);
423     pa_assert(name);
424
425     for (i = pa_http_protocol_servers(u->http); i; i = pa_strlist_next(i)) {
426         pa_parsed_address a;
427
428         if (pa_parse_address(pa_strlist_data(i), &a) >= 0 &&
429             (a.type == PA_PARSED_ADDRESS_TCP4 ||
430              a.type == PA_PARSED_ADDRESS_TCP6 ||
431              a.type == PA_PARSED_ADDRESS_TCP_AUTO)) {
432
433             const char *address;
434             char *s;
435
436             if (pa_is_ip_address(a.path_or_host))
437                 address = a.path_or_host;
438             else
439                 address = "@ADDRESS@";
440
441             if (a.port <= 0)
442                 a.port = 4714;
443
444             s = pa_sprintf_malloc("http://%s:%u/listen/source/%s", address, a.port, name);
445
446             pa_xfree(a.path_or_host);
447             return s;
448         }
449
450         pa_xfree(a.path_or_host);
451     }
452
453     return pa_sprintf_malloc("http://@ADDRESS@:4714/listen/source/%s", name);
454 }
455
456 static char **child_array(struct userdata *u, const char *path, unsigned *n) {
457     unsigned m;
458     uint32_t idx;
459     char **array;
460
461     pa_assert(u);
462     pa_assert(path);
463     pa_assert(n);
464
465     if (pa_streq(path, OBJECT_SINKS))
466         m = pa_idxset_size(u->core->sinks);
467     else
468         m = pa_idxset_size(u->core->sources);
469
470     array = pa_xnew(char*, m);
471     *n = 0;
472
473     if (pa_streq(path, OBJECT_SINKS)) {
474         pa_sink *sink;
475
476         PA_IDXSET_FOREACH(sink, u->core->sinks, idx)
477             array[(*n)++] = pa_sprintf_malloc(OBJECT_SINKS "/%u", sink->index);
478     } else {
479         pa_source *source;
480
481         PA_IDXSET_FOREACH(source, u->core->sources, idx)
482             if (!source->monitor_of)
483                 array[(*n)++] = pa_sprintf_malloc(OBJECT_SOURCES "/%u", source->index);
484     }
485
486     pa_assert((*n) <= m);
487
488     return array;
489 }
490
491 static void free_child_array(char **array, unsigned n) {
492
493     for (; n >= 1; n--)
494         pa_xfree(array[n-1]);
495
496     pa_xfree(array);
497 }
498
499 static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessage *m, void *userdata) {
500     struct userdata *u = userdata;
501     DBusMessage *r = NULL;
502     const char *path;
503
504     pa_assert(u);
505
506     path = dbus_message_get_path(m);
507
508     if (pa_streq(path, OBJECT_SINKS) || pa_streq(path, OBJECT_SOURCES)) {
509
510         /* Container nodes */
511
512         if (message_is_property_get(m, "org.Rygel.MediaContainer1", "Containers")) {
513             pa_assert_se(r = dbus_message_new_method_return(m));
514             append_variant_object_array(r, NULL, array_no_children, PA_ELEMENTSOF(array_no_children));
515
516         } else if (message_is_property_get(m, "org.Rygel.MediaContainer1", "ContainerCount")) {
517             pa_assert_se(r = dbus_message_new_method_return(m));
518             append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_no_children));
519
520         } else if (message_is_property_get(m, "org.Rygel.MediaContainer1", "Items")) {
521             char ** array;
522             unsigned n;
523
524             array = child_array(u, path, &n);
525
526             pa_assert_se(r = dbus_message_new_method_return(m));
527             append_variant_object_array(r, NULL, (const char**) array, n);
528
529             free_child_array(array, n);
530
531         } else if (message_is_property_get(m, "org.Rygel.MediaContainer1", "ItemCount")) {
532             pa_assert_se(r = dbus_message_new_method_return(m));
533             append_variant_unsigned(r, NULL,
534                                     pa_streq(path, OBJECT_SINKS) ?
535                                     pa_idxset_size(u->core->sinks) :
536                                     pa_idxset_size(u->core->sources));
537
538         } else if (message_is_property_get_all(m, "org.Rygel.MediaContainer1")) {
539             DBusMessageIter iter, sub;
540             char **array;
541             unsigned n;
542
543             pa_assert_se(r = dbus_message_new_method_return(m));
544             dbus_message_iter_init_append(r, &iter);
545
546             pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
547             append_property_dict_entry_object_array(r, &sub, "Containers", array_no_children, PA_ELEMENTSOF(array_no_children));
548             append_property_dict_entry_unsigned(r, &sub, "ContainerCount", 0);
549
550             array = child_array(u, path, &n);
551             append_property_dict_entry_object_array(r, &sub, "Items", (const char**) array, n);
552             free_child_array(array, n);
553             append_property_dict_entry_unsigned(r, &sub, "ItemCount",
554                                                 pa_streq(path, OBJECT_SINKS) ?
555                                                 pa_idxset_size(u->core->sinks) :
556                                                 pa_idxset_size(u->core->sources));
557
558             pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
559
560         } else if (message_is_property_get(m, "org.Rygel.MediaObject1", "Parent")) {
561             pa_assert_se(r = dbus_message_new_method_return(m));
562             append_variant_object(r, NULL, OBJECT_ROOT);
563
564         } else if (message_is_property_get(m, "org.Rygel.MediaObject1", "DisplayName")) {
565             pa_assert_se(r = dbus_message_new_method_return(m));
566             append_variant_string(r,
567                                   NULL,
568                                   pa_streq(path, OBJECT_SINKS) ?
569                                   _("Output Devices") :
570                                   _("Input Devices"));
571
572         } else if (message_is_property_get_all(m, "org.Rygel.MediaObject1")) {
573             DBusMessageIter iter, sub;
574
575             pa_assert_se(r = dbus_message_new_method_return(m));
576
577             dbus_message_iter_init_append(r, &iter);
578
579             pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
580             append_property_dict_entry_object(m, &sub, "Parent", OBJECT_ROOT);
581             append_property_dict_entry_string(m, &sub, "DisplayName",
582                                               pa_streq(path, OBJECT_SINKS) ?
583                                               _("Output Devices") :
584                                               _("Input Devices"));
585             pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
586
587         } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
588             pa_strbuf *sb;
589             char *xml;
590             uint32_t idx;
591
592             sb = pa_strbuf_new();
593             pa_strbuf_puts(sb, CONTAINER_INTROSPECT_XML_PREFIX);
594
595             if (pa_streq(path, OBJECT_SINKS)) {
596                 pa_sink *sink;
597
598                 PA_IDXSET_FOREACH(sink, u->core->sinks, idx)
599                     pa_strbuf_printf(sb, "<node name=\"%u\"/>", sink->index);
600             } else {
601                 pa_source *source;
602
603                 PA_IDXSET_FOREACH(source, u->core->sources, idx)
604                     if (!source->monitor_of)
605                         pa_strbuf_printf(sb, "<node name=\"%u\"/>", source->index);
606             }
607
608             pa_strbuf_puts(sb, CONTAINER_INTROSPECT_XML_POSTFIX);
609             xml = pa_strbuf_tostring_free(sb);
610
611             pa_assert_se(r = dbus_message_new_method_return(m));
612             pa_assert_se(dbus_message_append_args(
613                                  r,
614                                  DBUS_TYPE_STRING, &xml,
615                                  DBUS_TYPE_INVALID));
616
617             pa_xfree(xml);
618         } else
619             return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
620
621     } else {
622         pa_sink *sink = NULL;
623         pa_source *source = NULL;
624
625         /* Child nodes */
626
627         if (pa_startswith(path, OBJECT_SINKS "/"))
628             sink = pa_namereg_get(u->core, path + sizeof(OBJECT_SINKS), PA_NAMEREG_SINK);
629         else if (pa_startswith(path, OBJECT_SOURCES "/"))
630             source = pa_namereg_get(u->core, path + sizeof(OBJECT_SOURCES), PA_NAMEREG_SOURCE);
631
632         if (!sink && !source)
633             return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
634
635         if (message_is_property_get(m, "org.Rygel.MediaObject1", "Parent")) {
636             pa_assert_se(r = dbus_message_new_method_return(m));
637             append_variant_object(r, NULL, sink ? OBJECT_SINKS : OBJECT_SOURCES);
638
639         } else if (message_is_property_get(m, "org.Rygel.MediaObject1", "DisplayName")) {
640             pa_assert_se(r = dbus_message_new_method_return(m));
641             append_variant_string(r, NULL, pa_strna(pa_proplist_gets(sink ? sink->proplist : source->proplist, PA_PROP_DEVICE_DESCRIPTION)));
642
643         } else if (message_is_property_get_all(m, "org.Rygel.MediaObject1")) {
644             DBusMessageIter iter, sub;
645
646             pa_assert_se(r = dbus_message_new_method_return(m));
647             dbus_message_iter_init_append(r, &iter);
648
649             pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
650             append_property_dict_entry_object(r, &sub, "Parent", sink ? OBJECT_SINKS : OBJECT_SOURCES);
651             append_property_dict_entry_string(r, &sub, "DisplayName", pa_strna(pa_proplist_gets(sink ? sink->proplist : source->proplist, PA_PROP_DEVICE_DESCRIPTION)));
652             pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
653
654         } else if (message_is_property_get(m, "org.Rygel.MediaItem1", "Type")) {
655             pa_assert_se(r = dbus_message_new_method_return(m));
656             append_variant_string(r, NULL, "audio");
657
658         } else if (message_is_property_get(m, "org.Rygel.MediaItem1", "MIMEType")) {
659             char *t;
660
661             if (sink)
662                 t = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map);
663             else
664                 t = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map);
665
666             pa_assert_se(r = dbus_message_new_method_return(m));
667             append_variant_string(r, NULL, t);
668             pa_xfree(t);
669
670         } else if (message_is_property_get(m, "org.Rygel.MediaItem1", "URLs")) {
671             DBusMessageIter iter, sub, array;
672             char *url;
673
674             pa_assert_se(r = dbus_message_new_method_return(m));
675
676             dbus_message_iter_init_append(r, &iter);
677
678             url = compute_url(u, sink ? sink->monitor_source->name : source->name);
679
680             pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "as", &sub));
681             pa_assert_se(dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY, "s", &array));
682             pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &url));
683             pa_assert_se(dbus_message_iter_close_container(&sub, &array));
684             pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
685
686             pa_xfree(url);
687
688         } else if (message_is_property_get_all(m, "org.Rygel.MediaItem1")) {
689             DBusMessageIter iter, sub, dict, variant, array;
690             char *url, *t;
691             const char *un = "URLs";
692
693             pa_assert_se(r = dbus_message_new_method_return(m));
694             dbus_message_iter_init_append(r, &iter);
695
696             pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
697             append_property_dict_entry_string(r, &sub, "Type", "audio");
698
699             if (sink)
700                 t = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map);
701             else
702                 t = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map);
703
704             append_property_dict_entry_string(r, &sub, "MIMEType", t);
705             pa_xfree(t);
706
707             pa_assert_se(dbus_message_iter_open_container(&sub, DBUS_TYPE_DICT_ENTRY, NULL, &dict));
708             pa_assert_se(dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &un));
709
710             url = compute_url(u, sink ? sink->monitor_source->name : source->name);
711
712             pa_assert_se(dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, "as", &variant));
713             pa_assert_se(dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, "s", &array));
714             pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &url));
715             pa_assert_se(dbus_message_iter_close_container(&variant, &array));
716             pa_assert_se(dbus_message_iter_close_container(&dict, &variant));
717             pa_assert_se(dbus_message_iter_close_container(&sub, &dict));
718
719             pa_xfree(url);
720
721             pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
722
723         } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
724             const char *xml =
725                 ITEM_INTROSPECT_XML;
726
727             pa_assert_se(r = dbus_message_new_method_return(m));
728             pa_assert_se(dbus_message_append_args(
729                                  r,
730                                  DBUS_TYPE_STRING, &xml,
731                                  DBUS_TYPE_INVALID));
732
733         } else
734             return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
735     }
736
737     if (r) {
738         pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->bus), r, NULL));
739         dbus_message_unref(r);
740     }
741
742     return DBUS_HANDLER_RESULT_HANDLED;
743 }
744
745 int pa__init(pa_module *m) {
746
747     struct userdata *u;
748     pa_modargs *ma = NULL;
749     DBusError error;
750     const char *t;
751
752     static const DBusObjectPathVTable vtable_root = {
753         .message_function = root_handler,
754     };
755     static const DBusObjectPathVTable vtable_sinks_and_sources = {
756         .message_function = sinks_and_sources_handler,
757     };
758
759     dbus_error_init(&error);
760
761     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
762         pa_log("Failed to parse module arguments.");
763         goto fail;
764     }
765
766     m->userdata = u = pa_xnew0(struct userdata, 1);
767     u->core = m->core;
768     u->module = m;
769     u->http = pa_http_protocol_get(u->core);
770
771     if ((t = pa_modargs_get_value(ma, "display_name", NULL)))
772         u->display_name = pa_utf8_filter(t);
773     else
774         u->display_name = pa_xstrdup(_("Audio on @HOSTNAME@"));
775
776     u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE, (pa_hook_cb_t) source_new_or_unlink_cb, u);
777     u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) source_new_or_unlink_cb, u);
778
779     if (!(u->bus = pa_dbus_bus_get(m->core, DBUS_BUS_SESSION, &error))) {
780         pa_log("Failed to get session bus connection: %s", error.message);
781         goto fail;
782     }
783
784     pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(u->bus), OBJECT_ROOT, &vtable_root, u));
785     pa_assert_se(dbus_connection_register_fallback(pa_dbus_connection_get(u->bus), OBJECT_SINKS, &vtable_sinks_and_sources, u));
786     pa_assert_se(dbus_connection_register_fallback(pa_dbus_connection_get(u->bus), OBJECT_SOURCES, &vtable_sinks_and_sources, u));
787
788     if (dbus_bus_request_name(pa_dbus_connection_get(u->bus), SERVICE_NAME, DBUS_NAME_FLAG_DO_NOT_QUEUE, &error) != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
789         pa_log("Failed to request service name " SERVICE_NAME ": %s", error.message);
790         goto fail;
791     }
792
793     u->got_name = TRUE;
794
795     pa_modargs_free(ma);
796
797     return 0;
798
799 fail:
800     pa__done(m);
801
802     if (ma)
803         pa_modargs_free(ma);
804
805     dbus_error_free(&error);
806
807     return -1;
808 }
809
810 void pa__done(pa_module*m) {
811     struct userdata*u;
812     pa_assert(m);
813
814     if (!(u = m->userdata))
815         return;
816
817     if (u->source_new_slot)
818         pa_hook_slot_free(u->source_new_slot);
819     if (u->source_unlink_slot)
820         pa_hook_slot_free(u->source_unlink_slot);
821
822     if (u->bus) {
823         DBusError error;
824
825         dbus_error_init(&error);
826
827         dbus_connection_unregister_object_path(pa_dbus_connection_get(u->bus), OBJECT_ROOT);
828         dbus_connection_unregister_object_path(pa_dbus_connection_get(u->bus), OBJECT_SINKS);
829         dbus_connection_unregister_object_path(pa_dbus_connection_get(u->bus), OBJECT_SOURCES);
830
831         if (u->got_name) {
832             if (dbus_bus_release_name(pa_dbus_connection_get(u->bus), SERVICE_NAME, &error) != DBUS_RELEASE_NAME_REPLY_RELEASED) {
833                 pa_log("Failed to release service name " SERVICE_NAME ": %s", error.message);
834                 dbus_error_free(&error);
835             }
836         }
837
838         pa_dbus_connection_unref(u->bus);
839     }
840
841     pa_xfree(u->display_name);
842
843     if (u->http)
844         pa_http_protocol_unref(u->http);
845
846     pa_xfree(u);
847 }