Wrap up version 1.3.3.
[minidlna.git] / avahi.c
blob750542a2d24de35ec24c2aaaacdbdf770d7884cf
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.
24 #include <config.h>
26 #if defined(TIVO_SUPPORT) && defined(HAVE_AVAHI)
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <assert.h>
33 #include <time.h>
35 #include <avahi-common/error.h>
36 #include <avahi-common/thread-watch.h>
37 #include <avahi-client/publish.h>
39 #include "upnpglobalvars.h"
40 #include "log.h"
42 static struct context {
43 AvahiThreadedPoll *threaded_poll;
44 AvahiClient *client;
45 AvahiEntryGroup *group;
46 } ctx;
48 /*****************************************************************
49 * Private functions
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);
59 switch (state) {
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");
63 break;
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);
69 break;
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);
74 break;
75 case AVAHI_ENTRY_GROUP_UNCOMMITED:
76 case AVAHI_ENTRY_GROUP_REGISTERING:
77 default:
78 break;
82 static int
83 _add_svc(const char *cat, const char *srv, const char *id, const char *platform)
85 char name[128+1];
86 char path[64];
87 int ret;
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);
97 if (ret < 0)
98 DPRINTF(E_ERROR, L_SSDP, "Failed to add %s: %s\n",
99 srv, avahi_strerror(avahi_client_errno(ctx.client)));
101 return ret;
104 /* Try to register the TiVo DNS SRV service type. */
105 static void
106 register_stuff(void)
108 assert(ctx.client);
110 if (!ctx.group)
112 ctx.group = avahi_entry_group_new(ctx.client, publish_reply, NULL);
113 if (!ctx.group)
115 DPRINTF(E_ERROR, L_SSDP, "Failed to create entry group: %s\n",
116 avahi_strerror(avahi_client_errno(ctx.client)));
117 return;
121 if (avahi_entry_group_is_empty(ctx.group))
123 if (_add_svc("Music", "_tivo-music._tcp", "1", NULL) < 0)
124 return;
125 if (_add_svc("Photos", "_tivo-photos._tcp", "3", NULL) < 0)
126 return;
127 if (_add_svc("Videos", "_tivo-videostream._tcp", "2", "platform=pc/"SERVER_NAME) < 0)
128 return;
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)));
133 return;
138 static void client_callback(AvahiClient *client,
139 AvahiClientState state,
140 void *userdata)
142 ctx.client = client;
144 switch (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 */
148 register_stuff();
149 break;
150 case AVAHI_CLIENT_S_COLLISION:
151 case AVAHI_CLIENT_S_REGISTERING:
152 if (ctx.group)
153 avahi_entry_group_reset(ctx.group);
154 break;
155 case AVAHI_CLIENT_FAILURE:
156 if (avahi_client_errno(client) == AVAHI_ERR_DISCONNECTED)
158 int error;
160 avahi_client_free(ctx.client);
161 ctx.client = NULL;
162 ctx.group = NULL;
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,
168 &ctx, &error);
169 if (!ctx.client)
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);
177 else
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);
183 break;
184 case AVAHI_CLIENT_CONNECTING:
185 default:
186 break;
190 /************************************************************************
191 * Public funcions
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");
202 if (ctx.group)
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);
209 if (ctx.client)
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)
221 int error;
223 /* first of all we need to initialize our threading env */
224 ctx.threaded_poll = avahi_threaded_poll_new();
225 if (!ctx.threaded_poll)
226 return;
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);
231 if (!ctx.client)
233 DPRINTF(E_ERROR, L_SSDP, "Failed to create client object: %s\n",
234 avahi_strerror(error));
235 tivo_bonjour_unregister();
236 return;
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();
245 else
246 DPRINTF(E_INFO, L_SSDP, "Successfully started avahi loop\n");
248 #endif /* HAVE_AVAHI */