tivo: Add Avahi support
TiVo Bolt doesn't support the old TiVo Beacon discovery mechanism, so we need to support Bonjour discovery now. Use Avahi if it's available.
This commit is contained in:
parent
58a5f61d46
commit
0575299a87
@ -28,7 +28,7 @@ minidlnad_SOURCES = minidlna.c upnphttp.c upnpdescgen.c upnpsoap.c \
|
||||
sql.c utils.c metadata.c scanner.c inotify.c \
|
||||
tivo_utils.c tivo_beacon.c tivo_commands.c \
|
||||
playlist.c image_utils.c albumart.c log.c \
|
||||
containers.c tagutils/tagutils.c
|
||||
containers.c avahi.c tagutils/tagutils.c
|
||||
|
||||
#if NEED_VORBIS
|
||||
vorbisflag = -lvorbis
|
||||
@ -38,6 +38,10 @@ vorbisflag = -lvorbis
|
||||
flacoggflag = -logg
|
||||
#endif
|
||||
|
||||
#if HAVE_AVAHI
|
||||
avahilibs = -lavahi-client -lavahi-common
|
||||
#endif
|
||||
|
||||
minidlnad_LDADD = \
|
||||
@LIBJPEG_LIBS@ \
|
||||
@LIBID3TAG_LIBS@ \
|
||||
@ -47,7 +51,7 @@ minidlnad_LDADD = \
|
||||
@LIBEXIF_LIBS@ \
|
||||
@LIBINTL@ \
|
||||
@LIBICONV@ \
|
||||
-lFLAC $(flacoggflag) $(vorbisflag)
|
||||
-lFLAC $(flacoggflag) $(vorbisflag) $(avahilibs)
|
||||
|
||||
minidlnad_LDFLAGS = @STATIC_LDFLAGS@
|
||||
|
||||
|
271
avahi.c
Normal file
271
avahi.c
Normal file
@ -0,0 +1,271 @@
|
||||
/*
|
||||
* Author: Daniel S. Haischt <me@daniel.stefan.haischt.name>
|
||||
* Purpose: Avahi based Zeroconf support
|
||||
* Docs: http://avahi.org/download/doxygen/
|
||||
*
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#if defined(TIVO_SUPPORT) && defined(HAVE_AVAHI)
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <avahi-common/error.h>
|
||||
#include <avahi-common/thread-watch.h>
|
||||
#include <avahi-client/publish.h>
|
||||
|
||||
#include "upnpglobalvars.h"
|
||||
#include "log.h"
|
||||
|
||||
static struct context {
|
||||
/* Avahi stuff */
|
||||
AvahiThreadedPoll *threaded_poll;
|
||||
AvahiClient *client;
|
||||
AvahiEntryGroup *group;
|
||||
} ctx;
|
||||
|
||||
/*****************************************************************
|
||||
* Private functions
|
||||
*****************************************************************/
|
||||
|
||||
static void publish_reply(AvahiEntryGroup *g,
|
||||
AvahiEntryGroupState state,
|
||||
void *userdata);
|
||||
|
||||
/*
|
||||
* This function tries to register the AFP DNS
|
||||
* SRV service type.
|
||||
*/
|
||||
static void register_stuff(void) {
|
||||
char name[128+1];
|
||||
|
||||
assert(ctx.client);
|
||||
|
||||
if (!ctx.group) {
|
||||
if (!(ctx.group = avahi_entry_group_new(ctx.client, publish_reply, NULL))) {
|
||||
DPRINTF(E_ERROR, L_SSDP, "Failed to create entry group: %s\n",
|
||||
avahi_strerror(avahi_client_errno(ctx.client)));
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (avahi_entry_group_is_empty(ctx.group)) {
|
||||
/* Register our service */
|
||||
|
||||
DPRINTF(E_INFO, L_SSDP, "Registering '%s' with Avahi\n", friendly_name);
|
||||
|
||||
snprintf(name, sizeof(name), "Music on %s", friendly_name);
|
||||
if (avahi_entry_group_add_service(ctx.group,
|
||||
AVAHI_IF_UNSPEC,
|
||||
AVAHI_PROTO_UNSPEC,
|
||||
0,
|
||||
name,
|
||||
"_tivo-music._tcp",
|
||||
NULL,
|
||||
NULL,
|
||||
runtime_vars.port,
|
||||
"protocol=http",
|
||||
"path=/TiVoConnect?Command=QueryContainer&Container=1",
|
||||
NULL) < 0) {
|
||||
DPRINTF(E_ERROR, L_SSDP, "Failed to add service: %s\n",
|
||||
avahi_strerror(avahi_client_errno(ctx.client)));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
snprintf(name, sizeof(name), "Photos on %s", friendly_name);
|
||||
if (avahi_entry_group_add_service(ctx.group,
|
||||
AVAHI_IF_UNSPEC,
|
||||
AVAHI_PROTO_UNSPEC,
|
||||
0,
|
||||
name,
|
||||
"_tivo-photos._tcp",
|
||||
NULL,
|
||||
NULL,
|
||||
runtime_vars.port,
|
||||
"protocol=http",
|
||||
"path=/TiVoConnect?Command=QueryContainer&Container=3",
|
||||
NULL) < 0) {
|
||||
DPRINTF(E_ERROR, L_SSDP, "Failed to add service: %s\n",
|
||||
avahi_strerror(avahi_client_errno(ctx.client)));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
snprintf(name, sizeof(name), "Videos on %s", friendly_name);
|
||||
if (avahi_entry_group_add_service(ctx.group,
|
||||
AVAHI_IF_UNSPEC,
|
||||
AVAHI_PROTO_UNSPEC,
|
||||
0,
|
||||
name,
|
||||
"_tivo-videos._tcp",
|
||||
NULL,
|
||||
NULL,
|
||||
runtime_vars.port,
|
||||
"platform=pc/readydlna",
|
||||
"protocol=http",
|
||||
"path=/TiVoConnect?Command=QueryContainer&Container=2",
|
||||
NULL) < 0) {
|
||||
DPRINTF(E_ERROR, L_SSDP, "Failed to add service: %s\n",
|
||||
avahi_strerror(avahi_client_errno(ctx.client)));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (avahi_entry_group_commit(ctx.group) < 0) {
|
||||
DPRINTF(E_ERROR, L_SSDP, "Failed to commit entry group: %s\n",
|
||||
avahi_strerror(avahi_client_errno(ctx.client)));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
} /* if avahi_entry_group_is_empty*/
|
||||
|
||||
return;
|
||||
|
||||
fail:
|
||||
time(NULL);
|
||||
}
|
||||
|
||||
/* Called when publishing of service data completes */
|
||||
static void publish_reply(AvahiEntryGroup *g,
|
||||
AvahiEntryGroupState state,
|
||||
AVAHI_GCC_UNUSED void *userdata)
|
||||
{
|
||||
assert(ctx.group == NULL || g == ctx.group);
|
||||
|
||||
switch (state) {
|
||||
case AVAHI_ENTRY_GROUP_ESTABLISHED :
|
||||
/* The entry group has been established successfully */
|
||||
DPRINTF(E_MAXDEBUG, L_SSDP, "publish_reply: AVAHI_ENTRY_GROUP_ESTABLISHED\n");
|
||||
break;
|
||||
case AVAHI_ENTRY_GROUP_COLLISION:
|
||||
/* With multiple names there's no way to know which one collided */
|
||||
DPRINTF(E_ERROR, L_SSDP, "publish_reply: AVAHI_ENTRY_GROUP_COLLISION: %s\n",
|
||||
avahi_strerror(avahi_client_errno(ctx.client)));
|
||||
avahi_threaded_poll_quit(ctx.threaded_poll);
|
||||
break;
|
||||
case AVAHI_ENTRY_GROUP_FAILURE:
|
||||
DPRINTF(E_ERROR, L_SSDP, "Failed to register service: %s\n",
|
||||
avahi_strerror(avahi_client_errno(ctx.client)));
|
||||
avahi_threaded_poll_quit(ctx.threaded_poll);
|
||||
break;
|
||||
case AVAHI_ENTRY_GROUP_UNCOMMITED:
|
||||
break;
|
||||
case AVAHI_ENTRY_GROUP_REGISTERING:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void client_callback(AvahiClient *client,
|
||||
AvahiClientState state,
|
||||
void *userdata)
|
||||
{
|
||||
ctx.client = client;
|
||||
|
||||
switch (state) {
|
||||
case AVAHI_CLIENT_S_RUNNING:
|
||||
/* The server has startup successfully and registered its host
|
||||
* name on the network, so it's time to create our services */
|
||||
if (!ctx.group)
|
||||
register_stuff();
|
||||
break;
|
||||
|
||||
case AVAHI_CLIENT_S_COLLISION:
|
||||
if (ctx.group)
|
||||
avahi_entry_group_reset(ctx.group);
|
||||
break;
|
||||
|
||||
case AVAHI_CLIENT_FAILURE: {
|
||||
if (avahi_client_errno(client) == AVAHI_ERR_DISCONNECTED) {
|
||||
int error;
|
||||
|
||||
avahi_client_free(ctx.client);
|
||||
ctx.client = NULL;
|
||||
ctx.group = NULL;
|
||||
|
||||
/* Reconnect to the server */
|
||||
if (!(ctx.client = avahi_client_new(avahi_threaded_poll_get(ctx.threaded_poll),
|
||||
AVAHI_CLIENT_NO_FAIL,
|
||||
client_callback,
|
||||
&ctx,
|
||||
&error))) {
|
||||
|
||||
DPRINTF(E_ERROR, L_SSDP, "Failed to contact server: %s\n",
|
||||
avahi_strerror(error));
|
||||
|
||||
avahi_threaded_poll_quit(ctx.threaded_poll);
|
||||
}
|
||||
|
||||
} else {
|
||||
DPRINTF(E_ERROR, L_SSDP, "Client failure: %s\n",
|
||||
avahi_strerror(avahi_client_errno(client)));
|
||||
avahi_threaded_poll_quit(ctx.threaded_poll);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case AVAHI_CLIENT_S_REGISTERING:
|
||||
break;
|
||||
case AVAHI_CLIENT_CONNECTING:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************
|
||||
* Public funcions
|
||||
************************************************************************/
|
||||
|
||||
/*
|
||||
* Tries to shutdown this loop impl.
|
||||
* Call this function from inside this thread.
|
||||
*/
|
||||
void tivo_bonjour_unregister(void)
|
||||
{
|
||||
DPRINTF(E_DEBUG, L_SSDP, "tivo_bonjour_unregister\n");
|
||||
|
||||
if (ctx.threaded_poll)
|
||||
avahi_threaded_poll_stop(ctx.threaded_poll);
|
||||
if (ctx.client)
|
||||
avahi_client_free(ctx.client);
|
||||
if (ctx.threaded_poll)
|
||||
avahi_threaded_poll_free(ctx.threaded_poll);
|
||||
}
|
||||
|
||||
/*
|
||||
* Tries to setup the Zeroconf thread and any
|
||||
* neccessary config setting.
|
||||
*/
|
||||
void tivo_bonjour_register(void)
|
||||
{
|
||||
int error;
|
||||
|
||||
/* first of all we need to initialize our threading env */
|
||||
ctx.threaded_poll = avahi_threaded_poll_new();
|
||||
if (!ctx.threaded_poll)
|
||||
return;
|
||||
|
||||
/* now we need to acquire a client */
|
||||
ctx.client = avahi_client_new(avahi_threaded_poll_get(ctx.threaded_poll),
|
||||
AVAHI_CLIENT_NO_FAIL, client_callback, NULL, &error);
|
||||
if (!ctx.client)
|
||||
{
|
||||
DPRINTF(E_ERROR, L_SSDP, "Failed to create client object: %s\n",
|
||||
avahi_strerror(error));
|
||||
tivo_bonjour_unregister();
|
||||
return;
|
||||
}
|
||||
|
||||
if (avahi_threaded_poll_start(ctx.threaded_poll) < 0)
|
||||
{
|
||||
DPRINTF(E_ERROR, L_SSDP, "Failed to create thread: %s\n",
|
||||
avahi_strerror(avahi_client_errno(ctx.client)));
|
||||
tivo_bonjour_unregister();
|
||||
}
|
||||
else
|
||||
DPRINTF(E_INFO, L_SSDP, "Successfully started avahi loop.\n");
|
||||
}
|
||||
|
||||
#endif /* HAVE_AVAHI */
|
9
avahi.h
Normal file
9
avahi.h
Normal file
@ -0,0 +1,9 @@
|
||||
#include "config.h"
|
||||
|
||||
#if defined(TIVO_SUPPORT) && defined(HAVE_AVAHI)
|
||||
void tivo_bonjour_register(void);
|
||||
void tivo_bonjour_unregister(void);
|
||||
#else
|
||||
static inline void tivo_bonjour_register(void) {};
|
||||
static inline void tivo_bonjour_unregister(void) {};
|
||||
#endif
|
@ -478,6 +478,14 @@ AC_CHECK_LIB(vorbisfile, vorbis_comment_query,
|
||||
AM_CONDITIONAL(NEED_VORBIS, true),
|
||||
-logg)
|
||||
|
||||
AC_CHECK_LIB(avahi-client, avahi_threaded_poll_new,
|
||||
[AC_CHECK_HEADERS([avahi-common/thread-watch.h],
|
||||
AM_CONDITIONAL(HAVE_AVAHI, true)
|
||||
AC_DEFINE(HAVE_AVAHI,1,[Have avahi]),
|
||||
AM_CONDITIONAL(HAVE_AVAHI, false))],
|
||||
AM_CONDITIONAL(HAVE_AVAHI, false),
|
||||
-lavahi-client -lavahi-common)
|
||||
|
||||
################################################################################################################
|
||||
### Header checks
|
||||
|
||||
|
30
minidlna.c
30
minidlna.c
@ -94,6 +94,7 @@
|
||||
#include "log.h"
|
||||
#include "tivo_beacon.h"
|
||||
#include "tivo_utils.h"
|
||||
#include "avahi.h"
|
||||
|
||||
#if SQLITE_VERSION_NUMBER < 3005001
|
||||
# warning "Your SQLite3 library appears to be too old! Please use 3.5.1 or newer."
|
||||
@ -134,7 +135,7 @@ OpenAndConfHTTPSocket(unsigned short port)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (listen(s, 6) < 0)
|
||||
if (listen(s, 16) < 0)
|
||||
{
|
||||
DPRINTF(E_ERROR, L_GENERAL, "listen(http): %s\n", strerror(errno));
|
||||
close(s);
|
||||
@ -730,6 +731,10 @@ init(int argc, char **argv)
|
||||
if (strtobool(ary_options[i].value))
|
||||
SETFLAG(WIDE_LINKS_MASK);
|
||||
break;
|
||||
case TIVO_DISCOVERY:
|
||||
if (strcasecmp(ary_options[i].value, "beacon") == 0)
|
||||
CLEARFLAG(TIVO_BONJOUR_MASK);
|
||||
break;
|
||||
default:
|
||||
DPRINTF(E_ERROR, L_GENERAL, "Unknown option in file %s\n",
|
||||
optionsfile);
|
||||
@ -1069,14 +1074,21 @@ main(int argc, char **argv)
|
||||
ret = sqlite3_create_function(db, "tivorandom", 1, SQLITE_UTF8, NULL, &TiVoRandomSeedFunc, NULL, NULL);
|
||||
if (ret != SQLITE_OK)
|
||||
DPRINTF(E_ERROR, L_TIVO, "ERROR: Failed to add sqlite randomize function for TiVo!\n");
|
||||
/* open socket for sending Tivo notifications */
|
||||
sbeacon = OpenAndConfTivoBeaconSocket();
|
||||
if(sbeacon < 0)
|
||||
DPRINTF(E_FATAL, L_GENERAL, "Failed to open sockets for sending Tivo beacon notify "
|
||||
"messages. EXITING\n");
|
||||
tivo_bcast.sin_family = AF_INET;
|
||||
tivo_bcast.sin_addr.s_addr = htonl(getBcastAddress());
|
||||
tivo_bcast.sin_port = htons(2190);
|
||||
if (GETFLAG(TIVO_BONJOUR_MASK))
|
||||
{
|
||||
tivo_bonjour_register();
|
||||
}
|
||||
else
|
||||
{
|
||||
/* open socket for sending Tivo notifications */
|
||||
sbeacon = OpenAndConfTivoBeaconSocket();
|
||||
if(sbeacon < 0)
|
||||
DPRINTF(E_FATAL, L_GENERAL, "Failed to open sockets for sending Tivo beacon notify "
|
||||
"messages. EXITING\n");
|
||||
tivo_bcast.sin_family = AF_INET;
|
||||
tivo_bcast.sin_addr.s_addr = htonl(getBcastAddress());
|
||||
tivo_bcast.sin_port = htons(2190);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -65,7 +65,8 @@ static const struct {
|
||||
{ FORCE_SORT_CRITERIA, "force_sort_criteria" },
|
||||
{ MAX_CONNECTIONS, "max_connections" },
|
||||
{ MERGE_MEDIA_DIRS, "merge_media_dirs" },
|
||||
{ WIDE_LINKS, "wide_links" }
|
||||
{ WIDE_LINKS, "wide_links" },
|
||||
{ TIVO_DISCOVERY, "tivo_discovery" },
|
||||
};
|
||||
|
||||
int
|
||||
|
@ -58,7 +58,8 @@ enum upnpconfigoptions {
|
||||
FORCE_SORT_CRITERIA, /* force sorting by a given sort criteria */
|
||||
MAX_CONNECTIONS, /* maximum number of simultaneous connections */
|
||||
MERGE_MEDIA_DIRS, /* don't add an extra directory level when there are multiple media dirs */
|
||||
WIDE_LINKS /* allow following symlinks outside the defined media_dirs */
|
||||
WIDE_LINKS, /* allow following symlinks outside the defined media_dirs */
|
||||
TIVO_DISCOVERY, /* TiVo discovery protocol: bonjour or beacon. Defaults to bonjour if supported */
|
||||
};
|
||||
|
||||
/* readoptionsfile()
|
||||
|
@ -58,7 +58,7 @@
|
||||
time_t startup_time = 0;
|
||||
|
||||
struct runtime_vars_s runtime_vars;
|
||||
uint32_t runtime_flags = INOTIFY_MASK;
|
||||
uint32_t runtime_flags = INOTIFY_MASK | TIVO_BONJOUR_MASK;
|
||||
|
||||
const char *pidfilename = "/var/run/minidlna/minidlna.pid";
|
||||
|
||||
|
@ -188,6 +188,11 @@ extern uint32_t runtime_flags;
|
||||
#define SYSTEMD_MASK 0x0010
|
||||
#define MERGE_MEDIA_DIRS_MASK 0x0020
|
||||
#define WIDE_LINKS_MASK 0x0040
|
||||
#ifdef HAVE_AVAHI
|
||||
#define TIVO_BONJOUR_MASK 0x0080
|
||||
#else
|
||||
#define TIVO_BONJOUR_MASK 0x0000
|
||||
#endif
|
||||
|
||||
#define SETFLAG(mask) runtime_flags |= mask
|
||||
#define GETFLAG(mask) (runtime_flags & mask)
|
||||
|
Loading…
x
Reference in New Issue
Block a user