1 /* MiniDLNA media server
2 * Copyright (C) 2017 NETGEAR
4 * This file is part of MiniDLNA.
6 * MiniDLNA is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
10 * MiniDLNA is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>.
19 /* This file is loosely based on examples from zeroconf_avahi_demos:
20 * http://wiki.ros.org/zeroconf_avahi_demos
21 * with some ideas from Netatalk's Avahi implementation.
26 #if defined(TIVO_SUPPORT) && defined(HAVE_AVAHI)
35 #include <avahi-common/error.h>
36 #include <avahi-common/thread-watch.h>
37 #include <avahi-client/publish.h>
39 #include "upnpglobalvars.h"
42 static struct context
{
43 AvahiThreadedPoll
*threaded_poll
;
45 AvahiEntryGroup
*group
;
48 /*****************************************************************
50 *****************************************************************/
52 /* Called when publishing of service data completes */
53 static void publish_reply(AvahiEntryGroup
*g
,
54 AvahiEntryGroupState state
,
55 AVAHI_GCC_UNUSED
void *userdata
)
57 assert(ctx
.group
== NULL
|| g
== ctx
.group
);
60 case AVAHI_ENTRY_GROUP_ESTABLISHED
:
61 /* The entry group has been established successfully */
62 DPRINTF(E_MAXDEBUG
, L_SSDP
, "publish_reply: AVAHI_ENTRY_GROUP_ESTABLISHED\n");
64 case AVAHI_ENTRY_GROUP_COLLISION
:
65 /* With multiple names there's no way to know which one collided */
66 DPRINTF(E_ERROR
, L_SSDP
, "publish_reply: AVAHI_ENTRY_GROUP_COLLISION: %s\n",
67 avahi_strerror(avahi_client_errno(ctx
.client
)));
68 avahi_threaded_poll_quit(ctx
.threaded_poll
);
70 case AVAHI_ENTRY_GROUP_FAILURE
:
71 DPRINTF(E_ERROR
, L_SSDP
, "Failed to register service: %s\n",
72 avahi_strerror(avahi_client_errno(ctx
.client
)));
73 avahi_threaded_poll_quit(ctx
.threaded_poll
);
75 case AVAHI_ENTRY_GROUP_UNCOMMITED
:
76 case AVAHI_ENTRY_GROUP_REGISTERING
:
83 _add_svc(const char *cat
, const char *srv
, const char *id
, const char *platform
)
89 snprintf(name
, sizeof(name
), "%s on %s", cat
, friendly_name
);
90 snprintf(path
, sizeof(path
),
91 "path=/TiVoConnect?Command=QueryContainer&Container=%s", id
);
93 DPRINTF(E_INFO
, L_SSDP
, "Registering '%s'\n", name
);
94 ret
= avahi_entry_group_add_service(ctx
.group
, AVAHI_IF_UNSPEC
, AVAHI_PROTO_INET
,
95 0, name
, srv
, NULL
, NULL
, runtime_vars
.port
,
96 "protocol=http", path
, platform
, NULL
);
98 DPRINTF(E_ERROR
, L_SSDP
, "Failed to add %s: %s\n",
99 srv
, avahi_strerror(avahi_client_errno(ctx
.client
)));
104 /* Try to register the TiVo DNS SRV service type. */
112 ctx
.group
= avahi_entry_group_new(ctx
.client
, publish_reply
, NULL
);
115 DPRINTF(E_ERROR
, L_SSDP
, "Failed to create entry group: %s\n",
116 avahi_strerror(avahi_client_errno(ctx
.client
)));
121 if (avahi_entry_group_is_empty(ctx
.group
))
123 if (_add_svc("Music", "_tivo-music._tcp", "1", NULL
) < 0)
125 if (_add_svc("Photos", "_tivo-photos._tcp", "3", NULL
) < 0)
127 if (_add_svc("Videos", "_tivo-videostream._tcp", "2", "platform=pc/"SERVER_NAME
) < 0)
129 if (avahi_entry_group_commit(ctx
.group
) < 0)
131 DPRINTF(E_ERROR
, L_SSDP
, "Failed to commit entry group: %s\n",
132 avahi_strerror(avahi_client_errno(ctx
.client
)));
138 static void client_callback(AvahiClient
*client
,
139 AvahiClientState state
,
145 case AVAHI_CLIENT_S_RUNNING
:
146 /* The server has started up successfully and registered its host
147 * name on the network, so it's time to create our services */
150 case AVAHI_CLIENT_S_COLLISION
:
151 case AVAHI_CLIENT_S_REGISTERING
:
153 avahi_entry_group_reset(ctx
.group
);
155 case AVAHI_CLIENT_FAILURE
:
156 if (avahi_client_errno(client
) == AVAHI_ERR_DISCONNECTED
)
160 avahi_client_free(ctx
.client
);
164 /* Reconnect to the server */
165 ctx
.client
= avahi_client_new(
166 avahi_threaded_poll_get(ctx
.threaded_poll
),
167 AVAHI_CLIENT_NO_FAIL
, client_callback
,
171 DPRINTF(E_ERROR
, L_SSDP
,
172 "Failed to contact server: %s\n",
173 avahi_strerror(error
));
174 avahi_threaded_poll_quit(ctx
.threaded_poll
);
179 DPRINTF(E_ERROR
, L_SSDP
, "Client failure: %s\n",
180 avahi_strerror(avahi_client_errno(client
)));
181 avahi_threaded_poll_quit(ctx
.threaded_poll
);
184 case AVAHI_CLIENT_CONNECTING
:
190 /************************************************************************
192 ************************************************************************/
195 * Tries to shutdown this loop impl.
196 * Call this function from inside this thread.
198 void tivo_bonjour_unregister(void)
200 DPRINTF(E_DEBUG
, L_SSDP
, "tivo_bonjour_unregister\n");
204 avahi_entry_group_reset(ctx
.group
);
205 avahi_entry_group_free(ctx
.group
);
207 if (ctx
.threaded_poll
)
208 avahi_threaded_poll_stop(ctx
.threaded_poll
);
210 avahi_client_free(ctx
.client
);
211 if (ctx
.threaded_poll
)
212 avahi_threaded_poll_free(ctx
.threaded_poll
);
216 * Tries to setup the Zeroconf thread and any
217 * neccessary config setting.
219 void tivo_bonjour_register(void)
223 /* first of all we need to initialize our threading env */
224 ctx
.threaded_poll
= avahi_threaded_poll_new();
225 if (!ctx
.threaded_poll
)
228 /* now we need to acquire a client */
229 ctx
.client
= avahi_client_new(avahi_threaded_poll_get(ctx
.threaded_poll
),
230 AVAHI_CLIENT_NO_FAIL
, client_callback
, NULL
, &error
);
233 DPRINTF(E_ERROR
, L_SSDP
, "Failed to create client object: %s\n",
234 avahi_strerror(error
));
235 tivo_bonjour_unregister();
239 if (avahi_threaded_poll_start(ctx
.threaded_poll
) < 0)
241 DPRINTF(E_ERROR
, L_SSDP
, "Failed to create thread: %s\n",
242 avahi_strerror(avahi_client_errno(ctx
.client
)));
243 tivo_bonjour_unregister();
246 DPRINTF(E_INFO
, L_SSDP
, "Successfully started avahi loop\n");
248 #endif /* HAVE_AVAHI */