Git init
[framework/multimedia/pulseaudio.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.gnome.UPnP.MediaServer1.PulseAudio"
61
62 #define OBJECT_ROOT "/org/gnome/UPnP/MediaServer1/PulseAudio"
63 #define OBJECT_SINKS "/org/gnome/UPnP/MediaServer1/PulseAudio/Sinks"
64 #define OBJECT_SOURCES "/org/gnome/UPnP/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.gnome.UPnP.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.gnome.UPnP.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.gnome.UPnP.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.gnome.UPnP.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.gnome.UPnP.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.gnome.UPnP.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.gnome.UPnP.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.gnome.UPnP.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.gnome.UPnP.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.gnome.UPnP.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.gnome.UPnP.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.gnome.UPnP.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.gnome.UPnP.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         unsigned k;
469
470         m = pa_idxset_size(u->core->sources);
471         k = pa_idxset_size(u->core->sinks);
472
473         pa_assert(m >= k);
474
475         /* Subtract the monitor sources from the numbers of
476          * sources. There is one monitor source for each sink */
477         m -= k;
478     }
479
480     array = pa_xnew(char*, m);
481     *n = 0;
482
483     if (pa_streq(path, OBJECT_SINKS)) {
484         pa_sink *sink;
485
486         PA_IDXSET_FOREACH(sink, u->core->sinks, idx) {
487             pa_assert((*n) < m);
488             array[(*n)++] = pa_sprintf_malloc(OBJECT_SINKS "/%u", sink->index);
489         }
490     } else {
491         pa_source *source;
492
493         PA_IDXSET_FOREACH(source, u->core->sources, idx) {
494
495             if (!source->monitor_of) {
496                 pa_assert((*n) < m);
497                 array[(*n)++] = pa_sprintf_malloc(OBJECT_SOURCES "/%u", source->index);
498             }
499         }
500     }
501
502     pa_assert((*n) <= m);
503
504     return array;
505 }
506
507 static void free_child_array(char **array, unsigned n) {
508
509     for (; n >= 1; n--)
510         pa_xfree(array[n-1]);
511
512     pa_xfree(array);
513 }
514
515 static DBusHandlerResult sinks_and_sources_handler(DBusConnection *c, DBusMessage *m, void *userdata) {
516     struct userdata *u = userdata;
517     DBusMessage *r = NULL;
518     const char *path;
519
520     pa_assert(u);
521
522     path = dbus_message_get_path(m);
523
524     if (pa_streq(path, OBJECT_SINKS) || pa_streq(path, OBJECT_SOURCES)) {
525
526         /* Container nodes */
527
528         if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Containers")) {
529             pa_assert_se(r = dbus_message_new_method_return(m));
530             append_variant_object_array(r, NULL, array_no_children, PA_ELEMENTSOF(array_no_children));
531
532         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ContainerCount")) {
533             pa_assert_se(r = dbus_message_new_method_return(m));
534             append_variant_unsigned(r, NULL, PA_ELEMENTSOF(array_no_children));
535
536         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "Items")) {
537             char ** array;
538             unsigned n;
539
540             array = child_array(u, path, &n);
541
542             pa_assert_se(r = dbus_message_new_method_return(m));
543             append_variant_object_array(r, NULL, (const char**) array, n);
544
545             free_child_array(array, n);
546
547         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaContainer1", "ItemCount")) {
548             unsigned n, k;
549
550             n = pa_idxset_size(u->core->sinks);
551             k = pa_idxset_size(u->core->sources);
552             pa_assert(k >= n);
553
554             pa_assert_se(r = dbus_message_new_method_return(m));
555             append_variant_unsigned(r, NULL,
556                                     pa_streq(path, OBJECT_SINKS) ? n : k - n);
557
558         } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaContainer1")) {
559             DBusMessageIter iter, sub;
560             char **array;
561             unsigned n, k;
562
563             pa_assert_se(r = dbus_message_new_method_return(m));
564             dbus_message_iter_init_append(r, &iter);
565
566             pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
567             append_property_dict_entry_object_array(r, &sub, "Containers", array_no_children, PA_ELEMENTSOF(array_no_children));
568             append_property_dict_entry_unsigned(r, &sub, "ContainerCount", 0);
569
570             array = child_array(u, path, &n);
571             append_property_dict_entry_object_array(r, &sub, "Items", (const char**) array, n);
572             free_child_array(array, n);
573
574             n = pa_idxset_size(u->core->sinks);
575             k = pa_idxset_size(u->core->sources);
576             pa_assert(k >= n);
577
578             append_property_dict_entry_unsigned(r, &sub, "ItemCount",
579                                                 pa_streq(path, OBJECT_SINKS) ? n : k - n);
580
581             pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
582
583         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "Parent")) {
584             pa_assert_se(r = dbus_message_new_method_return(m));
585             append_variant_object(r, NULL, OBJECT_ROOT);
586
587         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "DisplayName")) {
588             pa_assert_se(r = dbus_message_new_method_return(m));
589             append_variant_string(r,
590                                   NULL,
591                                   pa_streq(path, OBJECT_SINKS) ?
592                                   _("Output Devices") :
593                                   _("Input Devices"));
594
595         } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject1")) {
596             DBusMessageIter iter, sub;
597
598             pa_assert_se(r = dbus_message_new_method_return(m));
599
600             dbus_message_iter_init_append(r, &iter);
601
602             pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
603             append_property_dict_entry_object(m, &sub, "Parent", OBJECT_ROOT);
604             append_property_dict_entry_string(m, &sub, "DisplayName",
605                                               pa_streq(path, OBJECT_SINKS) ?
606                                               _("Output Devices") :
607                                               _("Input Devices"));
608             pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
609
610         } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
611             pa_strbuf *sb;
612             char *xml;
613             uint32_t idx;
614
615             sb = pa_strbuf_new();
616             pa_strbuf_puts(sb, CONTAINER_INTROSPECT_XML_PREFIX);
617
618             if (pa_streq(path, OBJECT_SINKS)) {
619                 pa_sink *sink;
620
621                 PA_IDXSET_FOREACH(sink, u->core->sinks, idx)
622                     pa_strbuf_printf(sb, "<node name=\"%u\"/>", sink->index);
623             } else {
624                 pa_source *source;
625
626                 PA_IDXSET_FOREACH(source, u->core->sources, idx)
627                     if (!source->monitor_of)
628                         pa_strbuf_printf(sb, "<node name=\"%u\"/>", source->index);
629             }
630
631             pa_strbuf_puts(sb, CONTAINER_INTROSPECT_XML_POSTFIX);
632             xml = pa_strbuf_tostring_free(sb);
633
634             pa_assert_se(r = dbus_message_new_method_return(m));
635             pa_assert_se(dbus_message_append_args(
636                                  r,
637                                  DBUS_TYPE_STRING, &xml,
638                                  DBUS_TYPE_INVALID));
639
640             pa_xfree(xml);
641         } else
642             return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
643
644     } else {
645         pa_sink *sink = NULL;
646         pa_source *source = NULL;
647
648         /* Child nodes */
649
650         if (pa_startswith(path, OBJECT_SINKS "/"))
651             sink = pa_namereg_get(u->core, path + sizeof(OBJECT_SINKS), PA_NAMEREG_SINK);
652         else if (pa_startswith(path, OBJECT_SOURCES "/"))
653             source = pa_namereg_get(u->core, path + sizeof(OBJECT_SOURCES), PA_NAMEREG_SOURCE);
654
655         if (!sink && !source)
656             return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
657
658         if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "Parent")) {
659             pa_assert_se(r = dbus_message_new_method_return(m));
660             append_variant_object(r, NULL, sink ? OBJECT_SINKS : OBJECT_SOURCES);
661
662         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaObject1", "DisplayName")) {
663             pa_assert_se(r = dbus_message_new_method_return(m));
664             append_variant_string(r, NULL, pa_strna(pa_proplist_gets(sink ? sink->proplist : source->proplist, PA_PROP_DEVICE_DESCRIPTION)));
665
666         } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaObject1")) {
667             DBusMessageIter iter, sub;
668
669             pa_assert_se(r = dbus_message_new_method_return(m));
670             dbus_message_iter_init_append(r, &iter);
671
672             pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
673             append_property_dict_entry_object(r, &sub, "Parent", sink ? OBJECT_SINKS : OBJECT_SOURCES);
674             append_property_dict_entry_string(r, &sub, "DisplayName", pa_strna(pa_proplist_gets(sink ? sink->proplist : source->proplist, PA_PROP_DEVICE_DESCRIPTION)));
675             pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
676
677         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem1", "Type")) {
678             pa_assert_se(r = dbus_message_new_method_return(m));
679             append_variant_string(r, NULL, "audio");
680
681         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem1", "MIMEType")) {
682             char *t;
683
684             if (sink)
685                 t = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map);
686             else
687                 t = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map);
688
689             pa_assert_se(r = dbus_message_new_method_return(m));
690             append_variant_string(r, NULL, t);
691             pa_xfree(t);
692
693         } else if (message_is_property_get(m, "org.gnome.UPnP.MediaItem1", "URLs")) {
694             DBusMessageIter iter, sub, array;
695             char *url;
696
697             pa_assert_se(r = dbus_message_new_method_return(m));
698
699             dbus_message_iter_init_append(r, &iter);
700
701             url = compute_url(u, sink ? sink->monitor_source->name : source->name);
702
703             pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "as", &sub));
704             pa_assert_se(dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY, "s", &array));
705             pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &url));
706             pa_assert_se(dbus_message_iter_close_container(&sub, &array));
707             pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
708
709             pa_xfree(url);
710
711         } else if (message_is_property_get_all(m, "org.gnome.UPnP.MediaItem1")) {
712             DBusMessageIter iter, sub, dict, variant, array;
713             char *url, *t;
714             const char *un = "URLs";
715
716             pa_assert_se(r = dbus_message_new_method_return(m));
717             dbus_message_iter_init_append(r, &iter);
718
719             pa_assert_se(dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub));
720             append_property_dict_entry_string(r, &sub, "Type", "audio");
721
722             if (sink)
723                 t = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map);
724             else
725                 t = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map);
726
727             append_property_dict_entry_string(r, &sub, "MIMEType", t);
728             pa_xfree(t);
729
730             pa_assert_se(dbus_message_iter_open_container(&sub, DBUS_TYPE_DICT_ENTRY, NULL, &dict));
731             pa_assert_se(dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &un));
732
733             url = compute_url(u, sink ? sink->monitor_source->name : source->name);
734
735             pa_assert_se(dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, "as", &variant));
736             pa_assert_se(dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, "s", &array));
737             pa_assert_se(dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &url));
738             pa_assert_se(dbus_message_iter_close_container(&variant, &array));
739             pa_assert_se(dbus_message_iter_close_container(&dict, &variant));
740             pa_assert_se(dbus_message_iter_close_container(&sub, &dict));
741
742             pa_xfree(url);
743
744             pa_assert_se(dbus_message_iter_close_container(&iter, &sub));
745
746         } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
747             const char *xml =
748                 ITEM_INTROSPECT_XML;
749
750             pa_assert_se(r = dbus_message_new_method_return(m));
751             pa_assert_se(dbus_message_append_args(
752                                  r,
753                                  DBUS_TYPE_STRING, &xml,
754                                  DBUS_TYPE_INVALID));
755
756         } else
757             return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
758     }
759
760     if (r) {
761         pa_assert_se(dbus_connection_send(pa_dbus_connection_get(u->bus), r, NULL));
762         dbus_message_unref(r);
763     }
764
765     return DBUS_HANDLER_RESULT_HANDLED;
766 }
767
768 int pa__init(pa_module *m) {
769
770     struct userdata *u;
771     pa_modargs *ma = NULL;
772     DBusError error;
773     const char *t;
774
775     static const DBusObjectPathVTable vtable_root = {
776         .message_function = root_handler,
777     };
778     static const DBusObjectPathVTable vtable_sinks_and_sources = {
779         .message_function = sinks_and_sources_handler,
780     };
781
782     dbus_error_init(&error);
783
784     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
785         pa_log("Failed to parse module arguments.");
786         goto fail;
787     }
788
789     m->userdata = u = pa_xnew0(struct userdata, 1);
790     u->core = m->core;
791     u->module = m;
792     u->http = pa_http_protocol_get(u->core);
793
794     if ((t = pa_modargs_get_value(ma, "display_name", NULL)))
795         u->display_name = pa_utf8_filter(t);
796     else
797         u->display_name = pa_xstrdup(_("Audio on @HOSTNAME@"));
798
799     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);
800     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);
801
802     if (!(u->bus = pa_dbus_bus_get(m->core, DBUS_BUS_SESSION, &error))) {
803         pa_log("Failed to get session bus connection: %s", error.message);
804         goto fail;
805     }
806
807     pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(u->bus), OBJECT_ROOT, &vtable_root, u));
808     pa_assert_se(dbus_connection_register_fallback(pa_dbus_connection_get(u->bus), OBJECT_SINKS, &vtable_sinks_and_sources, u));
809     pa_assert_se(dbus_connection_register_fallback(pa_dbus_connection_get(u->bus), OBJECT_SOURCES, &vtable_sinks_and_sources, u));
810
811     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) {
812         pa_log("Failed to request service name " SERVICE_NAME ": %s", error.message);
813         goto fail;
814     }
815
816     u->got_name = TRUE;
817
818     pa_modargs_free(ma);
819
820     return 0;
821
822 fail:
823     pa__done(m);
824
825     if (ma)
826         pa_modargs_free(ma);
827
828     dbus_error_free(&error);
829
830     return -1;
831 }
832
833 void pa__done(pa_module*m) {
834     struct userdata*u;
835     pa_assert(m);
836
837     if (!(u = m->userdata))
838         return;
839
840     if (u->source_new_slot)
841         pa_hook_slot_free(u->source_new_slot);
842     if (u->source_unlink_slot)
843         pa_hook_slot_free(u->source_unlink_slot);
844
845     if (u->bus) {
846         DBusError error;
847
848         dbus_error_init(&error);
849
850         dbus_connection_unregister_object_path(pa_dbus_connection_get(u->bus), OBJECT_ROOT);
851         dbus_connection_unregister_object_path(pa_dbus_connection_get(u->bus), OBJECT_SINKS);
852         dbus_connection_unregister_object_path(pa_dbus_connection_get(u->bus), OBJECT_SOURCES);
853
854         if (u->got_name) {
855             if (dbus_bus_release_name(pa_dbus_connection_get(u->bus), SERVICE_NAME, &error) != DBUS_RELEASE_NAME_REPLY_RELEASED) {
856                 pa_log("Failed to release service name " SERVICE_NAME ": %s", error.message);
857                 dbus_error_free(&error);
858             }
859         }
860
861         pa_dbus_connection_unref(u->bus);
862     }
863
864     pa_xfree(u->display_name);
865
866     if (u->http)
867         pa_http_protocol_unref(u->http);
868
869     pa_xfree(u);
870 }