mirror of
https://gitlab.xiph.org/xiph/ezstream.git
synced 2025-02-02 15:07:45 -05:00
Refactor configuration (work in progress)
* Move -m and -n command line options into the config file * Restructure configuration file: - Group into "server", "stream", "media", "metadata", "decoders", and "encoders" - Untangle decoder and encoder: o Decoders match on file extensions and convert to a canonical "internal" format o Encoders create one of the supported stream formats, potentially using different parameters (like bitrate) - Consistently specify stream format - Enable reencoding by selecting an encoder * Architecturally separate configuration file storage from parsing - Allows for different configuration back-ends in the future, like YAML, SQL, REST API, ... * Support roll-back in case of error on (re)load * Anticipate HTTPS support
This commit is contained in:
parent
ffa2a01c96
commit
43e48648fa
3
NEWS
3
NEWS
@ -5,6 +5,9 @@ Changes in X.X.X, released on XXXX-XX-XX:
|
|||||||
* The behaviour of the -s command line argument was changed:
|
* The behaviour of the -s command line argument was changed:
|
||||||
To shuffle lines from standard input, the special file name "-" needs
|
To shuffle lines from standard input, the special file name "-" needs
|
||||||
to be provided.
|
to be provided.
|
||||||
|
* The command line options -m and -n have been removed, and new configuration
|
||||||
|
file settings have been added accordingly.
|
||||||
|
* The configuration file structure has changed.
|
||||||
|
|
||||||
|
|
||||||
Changes in 0.6.0, released on 2015-01-18:
|
Changes in 0.6.0, released on 2015-01-18:
|
||||||
|
67
examples/ezstream-full.xml
Normal file
67
examples/ezstream-full.xml
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<ezstream>
|
||||||
|
|
||||||
|
<server>
|
||||||
|
<protocol></protocol>
|
||||||
|
<hostname></hostname>
|
||||||
|
<port></port>
|
||||||
|
<user></user>
|
||||||
|
<password></password>
|
||||||
|
<ca_dir></ca_dir>
|
||||||
|
<ca_file></ca_file>
|
||||||
|
<client_cert></client_cert>
|
||||||
|
<client_key></client_key>
|
||||||
|
<reconnect_attempts></reconnect_attempts>
|
||||||
|
</server>
|
||||||
|
|
||||||
|
<stream>
|
||||||
|
<mountpoint></mountpoint>
|
||||||
|
<name></name>
|
||||||
|
<url></url>
|
||||||
|
<genre></genre>
|
||||||
|
<description></description>
|
||||||
|
<quality></quality>
|
||||||
|
<bitrate></bitrate>
|
||||||
|
<samplerate></samplerate>
|
||||||
|
<channels></channels>
|
||||||
|
<server_public></server_public>
|
||||||
|
<format></format>
|
||||||
|
<encoder></encoder>
|
||||||
|
</stream>
|
||||||
|
|
||||||
|
<media>
|
||||||
|
<type></type>
|
||||||
|
<filename></filename>
|
||||||
|
<shuffle></shuffle>
|
||||||
|
<stream_once></stream_once>
|
||||||
|
</media>
|
||||||
|
|
||||||
|
<metadata>
|
||||||
|
<program></program>
|
||||||
|
<format_str></format_str>
|
||||||
|
<refresh_interval></refresh_interval>
|
||||||
|
<normalize_strings></normalize_strings>
|
||||||
|
<no_updates></no_updates>
|
||||||
|
</metadata>
|
||||||
|
|
||||||
|
<decoders>
|
||||||
|
<decoder>
|
||||||
|
<name></name>
|
||||||
|
<program></program>
|
||||||
|
<file_ext></file_ext>
|
||||||
|
<!-- ... -->
|
||||||
|
</decoder>
|
||||||
|
<!-- ... -->
|
||||||
|
</decoders>
|
||||||
|
|
||||||
|
<encoders>
|
||||||
|
<encoder>
|
||||||
|
<name></name>
|
||||||
|
<format></format>
|
||||||
|
<program></program>
|
||||||
|
</encoder>
|
||||||
|
<!-- ... -->
|
||||||
|
</encoders>
|
||||||
|
|
||||||
|
</ezstream>
|
@ -6,7 +6,11 @@ bin_SCRIPTS = ezstream-file.sh
|
|||||||
noinst_HEADERS = \
|
noinst_HEADERS = \
|
||||||
attributes.h \
|
attributes.h \
|
||||||
cfg.h \
|
cfg.h \
|
||||||
configfile.h \
|
cfg_decoder.h \
|
||||||
|
cfg_encoder.h \
|
||||||
|
cfg_private.h \
|
||||||
|
cfg_xmlfile.h \
|
||||||
|
cmdline.h \
|
||||||
ezstream.h \
|
ezstream.h \
|
||||||
log.h \
|
log.h \
|
||||||
metadata.h \
|
metadata.h \
|
||||||
@ -15,7 +19,10 @@ noinst_HEADERS = \
|
|||||||
xalloc.h
|
xalloc.h
|
||||||
ezstream_SOURCES = \
|
ezstream_SOURCES = \
|
||||||
cfg.c \
|
cfg.c \
|
||||||
configfile.c \
|
cfg_decoder.c \
|
||||||
|
cfg_encoder.c \
|
||||||
|
cfg_xmlfile.c \
|
||||||
|
cmdline.c \
|
||||||
ezstream.c \
|
ezstream.c \
|
||||||
log.c \
|
log.c \
|
||||||
metadata.c \
|
metadata.c \
|
||||||
|
171
src/cfg.h
171
src/cfg.h
@ -17,19 +17,170 @@
|
|||||||
#ifndef __CFG_H__
|
#ifndef __CFG_H__
|
||||||
#define __CFG_H__
|
#define __CFG_H__
|
||||||
|
|
||||||
int cfg_cmdline_parse(int, char *[], int *);
|
#define CFG_SFMT_VORBIS "VORBIS"
|
||||||
|
#define CFG_SFMT_MP3 "MP3"
|
||||||
|
#define CFG_SFMT_THEORA "THEORA"
|
||||||
|
|
||||||
|
#define PLACEHOLDER_TRACK "@T@"
|
||||||
|
#define PLACEHOLDER_METADATA "@M@"
|
||||||
|
#define PLACEHOLDER_ARTIST "@a@"
|
||||||
|
#define PLACEHOLDER_TITLE "@t@"
|
||||||
|
#define PLACEHOLDER_STRING "@s@"
|
||||||
|
|
||||||
|
enum cfg_config_type {
|
||||||
|
CFG_TYPE_XMLFILE = 0,
|
||||||
|
CFG_TYPE_MIN = CFG_TYPE_XMLFILE,
|
||||||
|
CFG_TYPE_MAX = CFG_TYPE_XMLFILE,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum cfg_server_protocol {
|
||||||
|
CFG_PROTO_HTTP = 0,
|
||||||
|
CFG_PROTO_HTTPS,
|
||||||
|
CFG_PROTO_MIN = CFG_PROTO_HTTP,
|
||||||
|
CFG_PROTO_MAX = CFG_PROTO_HTTPS,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum cfg_media_type {
|
||||||
|
CFG_MEDIA_AUTODETECT = 0,
|
||||||
|
CFG_MEDIA_FILE,
|
||||||
|
CFG_MEDIA_PLAYLIST,
|
||||||
|
CFG_MEDIA_PROGRAM,
|
||||||
|
CFG_MEDIA_STDIN,
|
||||||
|
CFG_MEDIA_MIN = CFG_MEDIA_AUTODETECT,
|
||||||
|
CFG_MEDIA_MAX = CFG_MEDIA_STDIN,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum cfg_stream_format {
|
||||||
|
CFG_STREAM_INVALID = 0,
|
||||||
|
CFG_STREAM_VORBIS,
|
||||||
|
CFG_STREAM_MP3,
|
||||||
|
CFG_STREAM_THEORA,
|
||||||
|
CFG_STREAM_MIN = CFG_STREAM_VORBIS,
|
||||||
|
CFG_STREAM_MAX = CFG_STREAM_THEORA,
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "cfg_decoder.h"
|
||||||
|
#include "cfg_encoder.h"
|
||||||
|
|
||||||
|
int cfg_reload(void);
|
||||||
|
void cfg_exit(void);
|
||||||
|
|
||||||
|
int cfg_stream_str2fmt(const char *, enum cfg_stream_format *);
|
||||||
|
const char *
|
||||||
|
cfg_stream_fmt2str(enum cfg_stream_format);
|
||||||
|
|
||||||
|
int cfg_set_program_name(const char *, const char **);
|
||||||
|
int cfg_set_program_config_type(enum cfg_config_type, const char **);
|
||||||
|
int cfg_set_program_config_file(const char *, const char **);
|
||||||
|
int cfg_set_program_quiet_stderr(int, const char **);
|
||||||
|
int cfg_set_program_verbosity(unsigned int, const char **);
|
||||||
|
|
||||||
|
int cfg_set_server_protocol(const char *, const char **);
|
||||||
|
int cfg_set_server_hostname(const char *, const char **);
|
||||||
|
int cfg_set_server_port(const char *, const char **);
|
||||||
|
int cfg_set_server_user(const char *, const char **);
|
||||||
|
int cfg_set_server_password(const char *, const char **);
|
||||||
|
int cfg_set_server_ca_dir(const char *, const char **);
|
||||||
|
int cfg_set_server_ca_file(const char *, const char **);
|
||||||
|
int cfg_set_server_client_cert(const char *, const char **);
|
||||||
|
int cfg_set_server_client_key(const char *, const char **);
|
||||||
|
int cfg_set_server_reconnect_attempts(const char *, const char **);
|
||||||
|
|
||||||
|
int cfg_set_stream_mountpoint(const char *, const char **);
|
||||||
|
int cfg_set_stream_name(const char *, const char **);
|
||||||
|
int cfg_set_stream_url(const char *, const char **);
|
||||||
|
int cfg_set_stream_genre(const char *, const char **);
|
||||||
|
int cfg_set_stream_description(const char *, const char **);
|
||||||
|
int cfg_set_stream_quality(const char *, const char **);
|
||||||
|
int cfg_set_stream_bitrate(const char *, const char **);
|
||||||
|
int cfg_set_stream_samplerate(const char *, const char **);
|
||||||
|
int cfg_set_stream_channels(const char *, const char **);
|
||||||
|
int cfg_set_stream_server_public(const char *, const char **);
|
||||||
|
int cfg_set_stream_format(const char *, const char **);
|
||||||
|
int cfg_set_stream_encoder(const char *, const char **);
|
||||||
|
|
||||||
|
int cfg_set_media_type(const char *, const char **);
|
||||||
|
int cfg_set_media_filename(const char *, const char **);
|
||||||
|
int cfg_set_media_shuffle(const char *, const char **);
|
||||||
|
int cfg_set_media_stream_once(const char *, const char **);
|
||||||
|
|
||||||
|
int cfg_set_metadata_program(const char *, const char **);
|
||||||
|
int cfg_set_metadata_format_str(const char *, const char **);
|
||||||
|
int cfg_set_metadata_refresh_interval(const char *, const char **);
|
||||||
|
int cfg_set_metadata_normalize_strings(const char *, const char **);
|
||||||
|
int cfg_set_metadata_no_updates(const char *, const char **);
|
||||||
|
|
||||||
const char *
|
const char *
|
||||||
cfg_progname(void);
|
cfg_get_program_name(void);
|
||||||
|
enum cfg_config_type
|
||||||
|
cfg_get_program_config_type(void);
|
||||||
const char *
|
const char *
|
||||||
cfg_config_file(void);
|
cfg_get_program_config_file(void);
|
||||||
int cfg_no_metadata_updates(void);
|
int cfg_get_program_quiet_stderr(void);
|
||||||
int cfg_normalize_strings(void);
|
|
||||||
int cfg_quiet_stderr(void);
|
|
||||||
const char *
|
|
||||||
cfg_shuffle_file(void);
|
|
||||||
unsigned int
|
unsigned int
|
||||||
cfg_verbosity(void);
|
cfg_get_program_verbosity(void);
|
||||||
|
|
||||||
|
enum cfg_server_protocol
|
||||||
|
cfg_get_server_protocol(void);
|
||||||
|
const char *
|
||||||
|
cfg_get_server_protocol_str(void);
|
||||||
|
const char *
|
||||||
|
cfg_get_server_hostname(void);
|
||||||
|
unsigned int
|
||||||
|
cfg_get_server_port(void);
|
||||||
|
const char *
|
||||||
|
cfg_get_server_user(void);
|
||||||
|
const char *
|
||||||
|
cfg_get_server_password(void);
|
||||||
|
const char *
|
||||||
|
cfg_get_server_ca_dir(void);
|
||||||
|
const char *
|
||||||
|
cfg_get_server_ca_file(void);
|
||||||
|
const char *
|
||||||
|
cfg_get_server_client_cert(void);
|
||||||
|
const char *
|
||||||
|
cfg_get_server_client_key(void);
|
||||||
|
unsigned int
|
||||||
|
cfg_get_server_reconnect_attempts(void);
|
||||||
|
|
||||||
|
const char *
|
||||||
|
cfg_get_stream_mountpoint(void);
|
||||||
|
const char *
|
||||||
|
cfg_get_stream_name(void);
|
||||||
|
const char *
|
||||||
|
cfg_get_stream_url(void);
|
||||||
|
const char *
|
||||||
|
cfg_get_stream_genre(void);
|
||||||
|
const char *
|
||||||
|
cfg_get_stream_description(void);
|
||||||
|
const char *
|
||||||
|
cfg_get_stream_quality(void);
|
||||||
|
const char *
|
||||||
|
cfg_get_stream_bitrate(void);
|
||||||
|
const char *
|
||||||
|
cfg_get_stream_samplerate(void);
|
||||||
|
const char *
|
||||||
|
cfg_get_stream_channels(void);
|
||||||
|
int cfg_get_stream_server_public(void);
|
||||||
|
enum cfg_stream_format
|
||||||
|
cfg_get_stream_format(void);
|
||||||
|
const char *
|
||||||
|
cfg_get_stream_encoder(void);
|
||||||
|
|
||||||
|
enum cfg_media_type
|
||||||
|
cfg_get_media_type(void);
|
||||||
|
const char *
|
||||||
|
cfg_get_media_filename(void);
|
||||||
|
int cfg_get_media_shuffle(void);
|
||||||
|
int cfg_get_media_stream_once(void);
|
||||||
|
|
||||||
|
const char *
|
||||||
|
cfg_get_metadata_program(void);
|
||||||
|
const char *
|
||||||
|
cfg_get_metadata_format_str(void);
|
||||||
|
unsigned int
|
||||||
|
cfg_get_metadata_refresh_interval(void);
|
||||||
|
int cfg_get_metadata_normalize_strings(void);
|
||||||
|
int cfg_get_metadata_no_updates(void);
|
||||||
|
|
||||||
#endif /* __CFG_H__ */
|
#endif /* __CFG_H__ */
|
||||||
|
219
src/cfg_decoder.c
Normal file
219
src/cfg_decoder.c
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 Moritz Grimm <mgrimm@mrsserver.net>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
# include "config.h"
|
||||||
|
#endif /* HAVE_CONFIG_H */
|
||||||
|
|
||||||
|
#include <sys/queue.h>
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "cfg_private.h"
|
||||||
|
#include "cfg_decoder.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "xalloc.h"
|
||||||
|
|
||||||
|
struct file_ext {
|
||||||
|
TAILQ_ENTRY(file_ext) entry;
|
||||||
|
char *ext;
|
||||||
|
};
|
||||||
|
TAILQ_HEAD(file_ext_list, file_ext);
|
||||||
|
|
||||||
|
struct cfg_decoder {
|
||||||
|
TAILQ_ENTRY(cfg_decoder) entry;
|
||||||
|
char *name;
|
||||||
|
char *program;
|
||||||
|
struct file_ext_list exts;
|
||||||
|
};
|
||||||
|
TAILQ_HEAD(cfg_decoder_list, cfg_decoder);
|
||||||
|
|
||||||
|
static struct cfg_decoder_list cfg_decoders;
|
||||||
|
|
||||||
|
int
|
||||||
|
cfg_decoder_init(void)
|
||||||
|
{
|
||||||
|
TAILQ_INIT(&cfg_decoders);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
cfg_decoder_exit(void)
|
||||||
|
{
|
||||||
|
struct cfg_decoder *d;
|
||||||
|
|
||||||
|
while (NULL != (d = TAILQ_FIRST(&cfg_decoders))) {
|
||||||
|
struct file_ext *e;
|
||||||
|
|
||||||
|
TAILQ_REMOVE(&cfg_decoders, d, entry);
|
||||||
|
xfree(d->name);
|
||||||
|
xfree(d->program);
|
||||||
|
while (NULL != (e = TAILQ_FIRST(&d->exts))) {
|
||||||
|
TAILQ_REMOVE(&d->exts, e, entry);
|
||||||
|
xfree(e->ext);
|
||||||
|
xfree(e);
|
||||||
|
}
|
||||||
|
xfree(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct cfg_decoder *
|
||||||
|
cfg_decoder_get(const char *name)
|
||||||
|
{
|
||||||
|
struct cfg_decoder *d;
|
||||||
|
|
||||||
|
if (!name || !name[0])
|
||||||
|
return (NULL);
|
||||||
|
|
||||||
|
TAILQ_FOREACH(d, &cfg_decoders, entry) {
|
||||||
|
if (0 == strcasecmp(d->name, name))
|
||||||
|
return (d);
|
||||||
|
}
|
||||||
|
|
||||||
|
d = xcalloc(1UL, sizeof(*d));
|
||||||
|
d->name = xstrdup(name);
|
||||||
|
TAILQ_INIT(&d->exts);
|
||||||
|
|
||||||
|
return (d);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cfg_decoder_set_name(struct cfg_decoder *d, const char *name,
|
||||||
|
const char **errstrp)
|
||||||
|
{
|
||||||
|
if (!name || !name[0]) {
|
||||||
|
if (errstrp)
|
||||||
|
*errstrp = "empty";
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
xfree(d->name);
|
||||||
|
d->name = xstrdup(name);
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cfg_decoder_set_program(struct cfg_decoder *d, const char *program,
|
||||||
|
const char **errstrp)
|
||||||
|
{
|
||||||
|
if (!program || !program[0]) {
|
||||||
|
if (errstrp)
|
||||||
|
*errstrp = "empty";
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
xfree(d->program);
|
||||||
|
d->program = xstrdup(program);
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cfg_decoder_add_match(struct cfg_decoder *d, const char *ext,
|
||||||
|
const char **errstrp)
|
||||||
|
{
|
||||||
|
struct cfg_decoder *d2;
|
||||||
|
struct file_ext *e, *e2;
|
||||||
|
|
||||||
|
if (!ext || !ext[0]) {
|
||||||
|
if (errstrp)
|
||||||
|
*errstrp = "empty";
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
d2 = cfg_decoder_find(ext);
|
||||||
|
e = NULL;
|
||||||
|
if (d2) {
|
||||||
|
while (NULL != (e2 = TAILQ_FIRST(&d2->exts))) {
|
||||||
|
if (0 == strcasecmp(e2->ext, ext)) {
|
||||||
|
log_notice("%s: relocating match from %s to %s",
|
||||||
|
ext, d2->name, d->name);
|
||||||
|
TAILQ_REMOVE(&d2->exts, e2, entry);
|
||||||
|
e = e2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!e) {
|
||||||
|
e = xcalloc(1UL, sizeof(*e));
|
||||||
|
e->ext = xstrdup(ext);
|
||||||
|
}
|
||||||
|
TAILQ_INSERT_TAIL(&d->exts, e, entry);
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cfg_decoder_validate(struct cfg_decoder *d, const char **errstrp)
|
||||||
|
{
|
||||||
|
struct file_ext *e;
|
||||||
|
unsigned int num_exts;
|
||||||
|
|
||||||
|
if (!d->program) {
|
||||||
|
if (errstrp)
|
||||||
|
*errstrp = "program not set";
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
num_exts = 0;
|
||||||
|
TAILQ_FOREACH(e, &d->exts, entry) {
|
||||||
|
num_exts++;
|
||||||
|
}
|
||||||
|
if (!num_exts) {
|
||||||
|
if (errstrp)
|
||||||
|
*errstrp = "no file extensions registered";
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECKPH_PROHIBITED(d->program, PLACEHOLDER_STRING);
|
||||||
|
CHECKPH_DUPLICATE(d->program, PLACEHOLDER_TRACK);
|
||||||
|
CHECKPH_DUPLICATE(d->program, PLACEHOLDER_METADATA);
|
||||||
|
CHECKPH_DUPLICATE(d->program, PLACEHOLDER_ARTIST);
|
||||||
|
CHECKPH_DUPLICATE(d->program, PLACEHOLDER_TITLE);
|
||||||
|
CHECKPH_REQUIRED(d->program, PLACEHOLDER_TRACK);
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct cfg_decoder *
|
||||||
|
cfg_decoder_find(const char *ext)
|
||||||
|
{
|
||||||
|
struct cfg_decoder *d;
|
||||||
|
|
||||||
|
TAILQ_FOREACH(d, &cfg_decoders, entry) {
|
||||||
|
struct file_ext *e;
|
||||||
|
|
||||||
|
TAILQ_FOREACH(e, &d->exts, entry) {
|
||||||
|
if (0 == strcasecmp(e->ext, ext))
|
||||||
|
return (d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
cfg_decoder_get_name(struct cfg_decoder *d)
|
||||||
|
{
|
||||||
|
return (d->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
cfg_decoder_get_program(struct cfg_decoder *d)
|
||||||
|
{
|
||||||
|
return (d->program);
|
||||||
|
}
|
42
src/cfg_decoder.h
Normal file
42
src/cfg_decoder.h
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 Moritz Grimm <mgrimm@mrsserver.net>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __CFG_INPUT_H__
|
||||||
|
#define __CFG_INPUT_H__
|
||||||
|
|
||||||
|
typedef struct cfg_decoder * cfg_decoder_t;
|
||||||
|
|
||||||
|
int cfg_decoder_init(void);
|
||||||
|
void cfg_decoder_exit(void);
|
||||||
|
|
||||||
|
cfg_decoder_t
|
||||||
|
cfg_decoder_get(const char *);
|
||||||
|
|
||||||
|
int cfg_decoder_set_name(cfg_decoder_t, const char *, const char **);
|
||||||
|
int cfg_decoder_set_program(cfg_decoder_t, const char *, const char **);
|
||||||
|
int cfg_decoder_add_match(cfg_decoder_t, const char *, const char **);
|
||||||
|
|
||||||
|
int cfg_decoder_validate(cfg_decoder_t, const char **);
|
||||||
|
|
||||||
|
cfg_decoder_t
|
||||||
|
cfg_decoder_find(const char *);
|
||||||
|
|
||||||
|
const char *
|
||||||
|
cfg_decoder_get_name(cfg_decoder_t);
|
||||||
|
const char *
|
||||||
|
cfg_decoder_get_program(cfg_decoder_t);
|
||||||
|
|
||||||
|
#endif /* __CFG_INPUT_H__ */
|
184
src/cfg_encoder.c
Normal file
184
src/cfg_encoder.c
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 Moritz Grimm <mgrimm@mrsserver.net>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
# include "config.h"
|
||||||
|
#endif /* HAVE_CONFIG_H */
|
||||||
|
|
||||||
|
#include <sys/queue.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "cfg_private.h"
|
||||||
|
#include "cfg_encoder.h"
|
||||||
|
#include "xalloc.h"
|
||||||
|
|
||||||
|
struct cfg_encoder {
|
||||||
|
TAILQ_ENTRY(cfg_encoder) entry;
|
||||||
|
char *name;
|
||||||
|
enum cfg_stream_format format;
|
||||||
|
char *program;
|
||||||
|
};
|
||||||
|
TAILQ_HEAD(cfg_encoder_list, cfg_encoder);
|
||||||
|
|
||||||
|
static struct cfg_encoder_list cfg_encoders;
|
||||||
|
|
||||||
|
int
|
||||||
|
cfg_encoder_init(void)
|
||||||
|
{
|
||||||
|
TAILQ_INIT(&cfg_encoders);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
cfg_encoder_exit(void)
|
||||||
|
{
|
||||||
|
struct cfg_encoder *e;
|
||||||
|
|
||||||
|
while (NULL != (e = TAILQ_FIRST(&cfg_encoders))) {
|
||||||
|
TAILQ_REMOVE(&cfg_encoders, e, entry);
|
||||||
|
xfree(e->name);
|
||||||
|
xfree(e->program);
|
||||||
|
xfree(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct cfg_encoder *
|
||||||
|
cfg_encoder_get(const char *name)
|
||||||
|
{
|
||||||
|
struct cfg_encoder *e;
|
||||||
|
|
||||||
|
if (!name || !name[0])
|
||||||
|
return (NULL);
|
||||||
|
|
||||||
|
TAILQ_FOREACH(e, &cfg_encoders, entry) {
|
||||||
|
if (0 == strcasecmp(e->name, name))
|
||||||
|
return (e);
|
||||||
|
}
|
||||||
|
|
||||||
|
e = xcalloc(1UL, sizeof(*e));
|
||||||
|
e->name = xstrdup(name);
|
||||||
|
|
||||||
|
return (e);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cfg_encoder_set_name(struct cfg_encoder *e, const char *name,
|
||||||
|
const char **errstrp)
|
||||||
|
{
|
||||||
|
if (!name || !name[0]) {
|
||||||
|
if (errstrp)
|
||||||
|
*errstrp = "empty";
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
xfree(e->name);
|
||||||
|
e->name = xstrdup(name);
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cfg_encoder_set_format(struct cfg_encoder *e, enum cfg_stream_format fmt,
|
||||||
|
const char **not_used)
|
||||||
|
{
|
||||||
|
(void)not_used;
|
||||||
|
assert(CFG_STREAM_MIN <= fmt && CFG_STREAM_MAX >= fmt);
|
||||||
|
e->format = fmt;
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cfg_encoder_set_format_str(struct cfg_encoder *e, const char *fmt_str,
|
||||||
|
const char **errstrp)
|
||||||
|
{
|
||||||
|
enum cfg_stream_format fmt;
|
||||||
|
|
||||||
|
if (!fmt_str) {
|
||||||
|
if (errstrp)
|
||||||
|
*errstrp = "empty";
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 > cfg_stream_str2fmt(fmt_str, &fmt)) {
|
||||||
|
if (errstrp)
|
||||||
|
*errstrp = "unsupported stream format";
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg_encoder_set_format(e, fmt, errstrp);
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cfg_encoder_set_program(struct cfg_encoder *e, const char *program,
|
||||||
|
const char **errstrp)
|
||||||
|
{
|
||||||
|
if (!program || !program[0]) {
|
||||||
|
if (errstrp)
|
||||||
|
*errstrp = "empty";
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
xfree(e->program);
|
||||||
|
e->program = xstrdup(program);
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cfg_encoder_validate(struct cfg_encoder *e, const char **errstrp)
|
||||||
|
{
|
||||||
|
if (!e->program) {
|
||||||
|
if (errstrp)
|
||||||
|
*errstrp = "program not set";
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CFG_STREAM_INVALID == e->format) {
|
||||||
|
if (errstrp)
|
||||||
|
*errstrp = "format not set";
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECKPH_PROHIBITED(e->program, PLACEHOLDER_TRACK);
|
||||||
|
CHECKPH_PROHIBITED(e->program, PLACEHOLDER_STRING);
|
||||||
|
CHECKPH_DUPLICATE(e->program, PLACEHOLDER_METADATA);
|
||||||
|
CHECKPH_DUPLICATE(e->program, PLACEHOLDER_ARTIST);
|
||||||
|
CHECKPH_DUPLICATE(e->program, PLACEHOLDER_TITLE);
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
cfg_encoder_get_name(struct cfg_encoder *e)
|
||||||
|
{
|
||||||
|
return (e->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum cfg_stream_format
|
||||||
|
cfg_encoder_get_format(struct cfg_encoder *e)
|
||||||
|
{
|
||||||
|
return (e->format);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
cfg_encoder_get_program(struct cfg_encoder *e)
|
||||||
|
{
|
||||||
|
return (e->program);
|
||||||
|
}
|
43
src/cfg_encoder.h
Normal file
43
src/cfg_encoder.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 Moritz Grimm <mgrimm@mrsserver.net>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __CFG_ENCODER_H__
|
||||||
|
#define __CFG_ENCODER_H__
|
||||||
|
|
||||||
|
typedef struct cfg_encoder * cfg_encoder_t;
|
||||||
|
|
||||||
|
int cfg_encoder_init(void);
|
||||||
|
void cfg_encoder_exit(void);
|
||||||
|
|
||||||
|
cfg_encoder_t
|
||||||
|
cfg_encoder_get(const char *);
|
||||||
|
|
||||||
|
int cfg_encoder_set_name(cfg_encoder_t, const char *, const char **);
|
||||||
|
int cfg_encoder_set_format(cfg_encoder_t, enum cfg_stream_format,
|
||||||
|
const char **);
|
||||||
|
int cfg_encoder_set_format_str(cfg_encoder_t, const char *, const char **);
|
||||||
|
int cfg_encoder_set_program(cfg_encoder_t, const char *, const char **);
|
||||||
|
|
||||||
|
int cfg_encoder_validate(cfg_encoder_t, const char **);
|
||||||
|
|
||||||
|
const char *
|
||||||
|
cfg_encoder_get_name(cfg_encoder_t);
|
||||||
|
enum cfg_stream_format
|
||||||
|
cfg_encoder_get_format(cfg_encoder_t);
|
||||||
|
const char *
|
||||||
|
cfg_encoder_get_program(cfg_encoder_t);
|
||||||
|
|
||||||
|
#endif /* __CFG_ENCODER_H__ */
|
146
src/cfg_private.h
Normal file
146
src/cfg_private.h
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 Moritz Grimm <mgrimm@mrsserver.net>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __CFG_PRIVATE_H__
|
||||||
|
#define __CFG_PRIVATE_H__
|
||||||
|
|
||||||
|
#include "cfg.h"
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define EXTENSIONS_MAX 16
|
||||||
|
#define UCREDS_SIZE 256
|
||||||
|
|
||||||
|
#define DEFAULT_USER "source"
|
||||||
|
|
||||||
|
struct cfg {
|
||||||
|
struct program {
|
||||||
|
char name[PATH_MAX];
|
||||||
|
enum cfg_config_type config_type;
|
||||||
|
char config_file[PATH_MAX];
|
||||||
|
int quiet_stderr;
|
||||||
|
unsigned int verbosity;
|
||||||
|
} program;
|
||||||
|
struct server {
|
||||||
|
enum cfg_server_protocol protocol;
|
||||||
|
char hostname[NI_MAXHOST];
|
||||||
|
unsigned int port;
|
||||||
|
char user[UCREDS_SIZE];
|
||||||
|
char password[UCREDS_SIZE];
|
||||||
|
char ca_dir[PATH_MAX];
|
||||||
|
char ca_file[PATH_MAX];
|
||||||
|
char client_cert[PATH_MAX];
|
||||||
|
char client_key[PATH_MAX];
|
||||||
|
unsigned int reconnect_attempts;
|
||||||
|
} server;
|
||||||
|
struct stream {
|
||||||
|
char *mountpoint;
|
||||||
|
char *name;
|
||||||
|
char *url;
|
||||||
|
char *genre;
|
||||||
|
char *description;
|
||||||
|
char *quality;
|
||||||
|
char *bitrate;
|
||||||
|
char *samplerate;
|
||||||
|
char *channels;
|
||||||
|
int server_public;
|
||||||
|
enum cfg_stream_format format;
|
||||||
|
char *encoder;
|
||||||
|
} stream;
|
||||||
|
struct media {
|
||||||
|
enum cfg_media_type type;
|
||||||
|
char filename[PATH_MAX];
|
||||||
|
int shuffle;
|
||||||
|
int stream_once;
|
||||||
|
} media;
|
||||||
|
struct metadata {
|
||||||
|
char program[PATH_MAX];
|
||||||
|
char *format_str;
|
||||||
|
unsigned int refresh_interval;
|
||||||
|
int normalize_strings;
|
||||||
|
int no_updates;
|
||||||
|
} metadata;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SET_STRLCPY(t, s, e) do { \
|
||||||
|
if (!(s) || !(s)[0]) { \
|
||||||
|
if ((e)) \
|
||||||
|
*(e) = "empty"; \
|
||||||
|
return (-1); \
|
||||||
|
} \
|
||||||
|
if (sizeof((t)) <= \
|
||||||
|
strlcpy((t), (s), sizeof((t)))) { \
|
||||||
|
if ((e)) \
|
||||||
|
*(e) = "too long"; \
|
||||||
|
return (-1); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define SET_BOOLEAN(t, s, e) do { \
|
||||||
|
int val; \
|
||||||
|
if (!(s) || !(s)[0]) { \
|
||||||
|
if ((e)) \
|
||||||
|
*(e) = "empty"; \
|
||||||
|
return (-1); \
|
||||||
|
} \
|
||||||
|
if (0 == strcasecmp((s), "true") || \
|
||||||
|
0 == strcasecmp((s), "yes") || \
|
||||||
|
0 == strcasecmp((s), "1")) { \
|
||||||
|
val = 1; \
|
||||||
|
} else if (0 == strcasecmp((s), "false") || \
|
||||||
|
0 == strcasecmp((s), "no") || \
|
||||||
|
0 == strcasecmp((s), "0")) { \
|
||||||
|
val = 0; \
|
||||||
|
} else { \
|
||||||
|
if ((e)) \
|
||||||
|
*(e) = "invalid"; \
|
||||||
|
return (-1); \
|
||||||
|
} \
|
||||||
|
(t) = val; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define CHECKPH_PROHIBITED(s, p) do { \
|
||||||
|
if (NULL != strstr((s), (p))) { \
|
||||||
|
if (errstrp) \
|
||||||
|
*errstrp = "prohibited placeholder " p; \
|
||||||
|
return (-1); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define CHECKPH_DUPLICATE(s, p) do { \
|
||||||
|
char *c; \
|
||||||
|
if (NULL != (c = strstr((s), (p)))) { \
|
||||||
|
c += strlen((p)); \
|
||||||
|
if (NULL != strstr(c, (p))) { \
|
||||||
|
if (errstrp) \
|
||||||
|
*errstrp = "duplicate placeholder " p; \
|
||||||
|
return (-1); \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define CHECKPH_REQUIRED(s, p) do { \
|
||||||
|
if (NULL == strstr((s), (p))) { \
|
||||||
|
if (errstrp) \
|
||||||
|
*errstrp = "missing placeholder " p; \
|
||||||
|
return (-1); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#endif /* __CFG_PRIVATE_H__ */
|
383
src/cfg_xmlfile.c
Normal file
383
src/cfg_xmlfile.c
Normal file
@ -0,0 +1,383 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 Moritz Grimm <mgrimm@mrsserver.net>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
# include "config.h"
|
||||||
|
#endif /* HAVE_CONFIG_H */
|
||||||
|
|
||||||
|
#include "cfg.h"
|
||||||
|
#include "cfg_xmlfile.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "xalloc.h"
|
||||||
|
|
||||||
|
#include <libxml/parser.h>
|
||||||
|
|
||||||
|
static unsigned int decoder_id, encoder_id;
|
||||||
|
|
||||||
|
static int _cfg_xmlfile_parse_server(xmlDocPtr, xmlNodePtr);
|
||||||
|
static int _cfg_xmlfile_parse_stream(xmlDocPtr, xmlNodePtr);
|
||||||
|
static int _cfg_xmlfile_parse_media(xmlDocPtr, xmlNodePtr);
|
||||||
|
static int _cfg_xmlfile_parse_metadata(xmlDocPtr, xmlNodePtr);
|
||||||
|
static int _cfg_xmlfile_parse_decoder(xmlDocPtr, xmlNodePtr);
|
||||||
|
static int _cfg_xmlfile_parse_decoders(xmlDocPtr, xmlNodePtr);
|
||||||
|
static int _cfg_xmlfile_parse_encoder(xmlDocPtr, xmlNodePtr);
|
||||||
|
static int _cfg_xmlfile_parse_encoders(xmlDocPtr, xmlNodePtr);
|
||||||
|
|
||||||
|
#define XML_CHAR(s) (const xmlChar *)(s)
|
||||||
|
#define XML_STRCONFIG(s, f, e) do { \
|
||||||
|
if (0 == xmlStrcasecmp(cur->name, XML_CHAR((e)))) { \
|
||||||
|
xmlChar *val; \
|
||||||
|
const char *err_str; \
|
||||||
|
\
|
||||||
|
val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); \
|
||||||
|
if (0 > (f)(val, &err_str)) { \
|
||||||
|
log_error("%s[%ld]: %s: %s: %s", doc->name, \
|
||||||
|
xmlGetLineNo(cur), (s), (e), err_str); \
|
||||||
|
error = 1; \
|
||||||
|
} \
|
||||||
|
xmlFree(val); \
|
||||||
|
continue; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
static int
|
||||||
|
_cfg_xmlfile_parse_server(xmlDocPtr doc, xmlNodePtr cur)
|
||||||
|
{
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
for (cur = cur->xmlChildrenNode; cur; cur = cur->next) {
|
||||||
|
XML_STRCONFIG("server", cfg_set_server_protocol, "protocol");
|
||||||
|
XML_STRCONFIG("server", cfg_set_server_hostname, "hostname");
|
||||||
|
XML_STRCONFIG("server", cfg_set_server_port, "port");
|
||||||
|
XML_STRCONFIG("server", cfg_set_server_user, "user");
|
||||||
|
XML_STRCONFIG("server", cfg_set_server_password, "password");
|
||||||
|
XML_STRCONFIG("server", cfg_set_server_ca_dir, "ca_dir");
|
||||||
|
XML_STRCONFIG("server", cfg_set_server_ca_file, "ca_file");
|
||||||
|
XML_STRCONFIG("server", cfg_set_server_client_key, "client_key");
|
||||||
|
XML_STRCONFIG("server", cfg_set_server_client_cert, "client_cert");
|
||||||
|
XML_STRCONFIG("server", cfg_set_server_reconnect_attempts,
|
||||||
|
"reconnect_attempts");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
_cfg_xmlfile_parse_stream(xmlDocPtr doc, xmlNodePtr cur)
|
||||||
|
{
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
for (cur = cur->xmlChildrenNode; cur; cur = cur->next) {
|
||||||
|
XML_STRCONFIG("stream", cfg_set_stream_mountpoint, "mountpoint");
|
||||||
|
XML_STRCONFIG("stream", cfg_set_stream_name, "name");
|
||||||
|
XML_STRCONFIG("stream", cfg_set_stream_url, "url");
|
||||||
|
XML_STRCONFIG("stream", cfg_set_stream_genre, "genre");
|
||||||
|
XML_STRCONFIG("stream", cfg_set_stream_description, "description");
|
||||||
|
XML_STRCONFIG("stream", cfg_set_stream_quality, "quality");
|
||||||
|
XML_STRCONFIG("stream", cfg_set_stream_bitrate, "bitrate");
|
||||||
|
XML_STRCONFIG("stream", cfg_set_stream_samplerate, "samplerate");
|
||||||
|
XML_STRCONFIG("stream", cfg_set_stream_channels, "channels");
|
||||||
|
XML_STRCONFIG("stream", cfg_set_stream_server_public, "server_public");
|
||||||
|
XML_STRCONFIG("stream", cfg_set_stream_format, "format");
|
||||||
|
XML_STRCONFIG("stream", cfg_set_stream_encoder, "encoder");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
_cfg_xmlfile_parse_media(xmlDocPtr doc, xmlNodePtr cur)
|
||||||
|
{
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
for (cur = cur->xmlChildrenNode; cur; cur = cur->next) {
|
||||||
|
XML_STRCONFIG("media", cfg_set_media_type, "type");
|
||||||
|
XML_STRCONFIG("media", cfg_set_media_filename, "filename");
|
||||||
|
XML_STRCONFIG("media", cfg_set_media_shuffle, "shuffle");
|
||||||
|
XML_STRCONFIG("media", cfg_set_media_stream_once, "stream_once");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
_cfg_xmlfile_parse_metadata(xmlDocPtr doc, xmlNodePtr cur)
|
||||||
|
{
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
for (cur = cur->xmlChildrenNode; cur; cur = cur->next) {
|
||||||
|
XML_STRCONFIG("metadata", cfg_set_metadata_program, "program");
|
||||||
|
XML_STRCONFIG("metadata", cfg_set_metadata_format_str, "format_str");
|
||||||
|
XML_STRCONFIG("metadata", cfg_set_metadata_refresh_interval,
|
||||||
|
"refresh_interval");
|
||||||
|
XML_STRCONFIG("metadata", cfg_set_metadata_normalize_strings,
|
||||||
|
"normalize_strings");
|
||||||
|
XML_STRCONFIG("metadata", cfg_set_metadata_no_updates, "no_updates");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define XML_DECODER_SET(c, f, e) do { \
|
||||||
|
if (0 == xmlStrcasecmp(cur->name, XML_CHAR((e)))) { \
|
||||||
|
xmlChar *val; \
|
||||||
|
const char *err_str; \
|
||||||
|
int error = 0; \
|
||||||
|
\
|
||||||
|
val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); \
|
||||||
|
if (0 > (f)((c), val, &err_str)) { \
|
||||||
|
log_error("%s[%ld]: decoder: %s: %s: %s", \
|
||||||
|
doc->name, xmlGetLineNo(cur), \
|
||||||
|
cfg_decoder_get_name((c)), (e), err_str); \
|
||||||
|
error = 1; \
|
||||||
|
} \
|
||||||
|
xmlFree(val); \
|
||||||
|
if (error) \
|
||||||
|
return (-1); \
|
||||||
|
continue; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
static int
|
||||||
|
_cfg_xmlfile_parse_decoder(xmlDocPtr doc, xmlNodePtr cur)
|
||||||
|
{
|
||||||
|
cfg_decoder_t d;
|
||||||
|
char d_id[11];
|
||||||
|
const char *errstr;
|
||||||
|
long int line_no = xmlGetLineNo(cur);
|
||||||
|
|
||||||
|
(void)snprintf(d_id, sizeof(d_id), "%u", ++decoder_id);
|
||||||
|
d = cfg_decoder_get(d_id);
|
||||||
|
|
||||||
|
for (cur = cur->xmlChildrenNode; cur; cur = cur->next) {
|
||||||
|
XML_DECODER_SET(d, cfg_decoder_set_name, "name");
|
||||||
|
XML_DECODER_SET(d, cfg_decoder_set_program, "program");
|
||||||
|
XML_DECODER_SET(d, cfg_decoder_add_match, "file_ext");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 > cfg_decoder_validate(d, &errstr)) {
|
||||||
|
log_error("%s[%ld]: decoder: %s", doc->name, line_no, errstr);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
_cfg_xmlfile_parse_decoders(xmlDocPtr doc, xmlNodePtr cur)
|
||||||
|
{
|
||||||
|
for (cur = cur->xmlChildrenNode; cur; cur = cur->next) {
|
||||||
|
if (0 == xmlStrcasecmp(cur->name, XML_CHAR("decoder")) &&
|
||||||
|
0 > _cfg_xmlfile_parse_decoder(doc, cur))
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define XML_ENCODER_SET(c, f, e) do { \
|
||||||
|
if (0 == xmlStrcasecmp(cur->name, XML_CHAR((e)))) { \
|
||||||
|
xmlChar *val; \
|
||||||
|
const char *err_str; \
|
||||||
|
int error = 0; \
|
||||||
|
\
|
||||||
|
val = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); \
|
||||||
|
if (0 > (f)((c), val, &err_str)) { \
|
||||||
|
log_error("%s[%ld]: encoder: %s: %s: %s", \
|
||||||
|
doc->name, xmlGetLineNo(cur), \
|
||||||
|
cfg_encoder_get_name((c)), (e), err_str); \
|
||||||
|
error = 1; \
|
||||||
|
} \
|
||||||
|
xmlFree(val); \
|
||||||
|
if (error) \
|
||||||
|
return (-1); \
|
||||||
|
continue; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
static int
|
||||||
|
_cfg_xmlfile_parse_encoder(xmlDocPtr doc, xmlNodePtr cur)
|
||||||
|
{
|
||||||
|
cfg_encoder_t e;
|
||||||
|
char e_id[11];
|
||||||
|
const char *errstr;
|
||||||
|
long int line_no = xmlGetLineNo(cur);
|
||||||
|
|
||||||
|
(void)snprintf(e_id, sizeof(e_id), "%u", ++encoder_id);
|
||||||
|
e = cfg_encoder_get(e_id);
|
||||||
|
|
||||||
|
for (cur = cur->xmlChildrenNode; cur; cur = cur->next) {
|
||||||
|
XML_ENCODER_SET(e, cfg_encoder_set_name, "name");
|
||||||
|
XML_ENCODER_SET(e, cfg_encoder_set_format_str, "format");
|
||||||
|
XML_ENCODER_SET(e, cfg_encoder_set_program, "program");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 > cfg_encoder_validate(e, &errstr)) {
|
||||||
|
log_error("%s[%ld]: encoder: %s", doc->name, line_no, errstr);
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
_cfg_xmlfile_parse_encoders(xmlDocPtr doc, xmlNodePtr cur)
|
||||||
|
{
|
||||||
|
for (cur = cur->xmlChildrenNode; cur; cur = cur->next) {
|
||||||
|
if (0 == xmlStrcasecmp(cur->name, XML_CHAR("encoder")) &&
|
||||||
|
0 > _cfg_xmlfile_parse_encoder(doc, cur))
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* XML configuration file structure:
|
||||||
|
*
|
||||||
|
* ezstream
|
||||||
|
* server
|
||||||
|
* protocol
|
||||||
|
* hostname
|
||||||
|
* port
|
||||||
|
* user
|
||||||
|
* password
|
||||||
|
* ca_dir
|
||||||
|
* ca_file
|
||||||
|
* client_cert
|
||||||
|
* client_key
|
||||||
|
* reconnect_attempts
|
||||||
|
* stream
|
||||||
|
* mountpoint
|
||||||
|
* name
|
||||||
|
* url
|
||||||
|
* genre
|
||||||
|
* description
|
||||||
|
* quality
|
||||||
|
* bitrate
|
||||||
|
* samplerate
|
||||||
|
* channels
|
||||||
|
* server_public
|
||||||
|
* format
|
||||||
|
* encoder
|
||||||
|
* media
|
||||||
|
* type
|
||||||
|
* filename
|
||||||
|
* shuffle
|
||||||
|
* stream_once
|
||||||
|
* metadata
|
||||||
|
* program
|
||||||
|
* format_str
|
||||||
|
* refresh_interval
|
||||||
|
* normalize_strings
|
||||||
|
* no_updates
|
||||||
|
* decoders
|
||||||
|
* decoder
|
||||||
|
* name
|
||||||
|
* program
|
||||||
|
* file_ext
|
||||||
|
* ...
|
||||||
|
* ...
|
||||||
|
* encoders
|
||||||
|
* encoder
|
||||||
|
* name
|
||||||
|
* format
|
||||||
|
* program
|
||||||
|
* ...
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
cfg_xmlfile_parse(const char *config_file)
|
||||||
|
{
|
||||||
|
xmlDocPtr doc = NULL;
|
||||||
|
xmlNodePtr cur = NULL;
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
xmlLineNumbersDefault(1);
|
||||||
|
|
||||||
|
doc = xmlParseFile(config_file);
|
||||||
|
if (!doc) {
|
||||||
|
log_error("%s: not well-formed XML", config_file);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if (!doc->name)
|
||||||
|
doc->name = xmlStrdup(config_file);
|
||||||
|
cur = xmlDocGetRootElement(doc);
|
||||||
|
if (!cur) {
|
||||||
|
log_error("%s: empty document", config_file);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if (0 != xmlStrcasecmp(cur->name, XML_CHAR("ezstream"))) {
|
||||||
|
log_error("%s: not ezstream config", config_file);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (cur = cur->xmlChildrenNode; cur; cur = cur->next) {
|
||||||
|
if (0 == xmlStrcasecmp(cur->name, XML_CHAR("server"))) {
|
||||||
|
if (0 > _cfg_xmlfile_parse_server(doc, cur))
|
||||||
|
error = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (0 == xmlStrcasecmp(cur->name, XML_CHAR("stream"))) {
|
||||||
|
if (0 > _cfg_xmlfile_parse_stream(doc, cur))
|
||||||
|
error = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (0 == xmlStrcasecmp(cur->name, XML_CHAR("media"))) {
|
||||||
|
if (0 > _cfg_xmlfile_parse_media(doc, cur))
|
||||||
|
error = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (0 == xmlStrcasecmp(cur->name, XML_CHAR("metadata"))) {
|
||||||
|
if (0 > _cfg_xmlfile_parse_metadata(doc, cur))
|
||||||
|
error = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (0 == xmlStrcasecmp(cur->name, XML_CHAR("decoders"))) {
|
||||||
|
if (0 > _cfg_xmlfile_parse_decoders(doc, cur))
|
||||||
|
error = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (0 == xmlStrcasecmp(cur->name, XML_CHAR("encoders"))) {
|
||||||
|
if (0 > _cfg_xmlfile_parse_encoders(doc, cur))
|
||||||
|
error = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (error)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
xmlFreeDoc(doc);
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
|
||||||
|
error:
|
||||||
|
if (doc)
|
||||||
|
xmlFreeDoc(doc);
|
||||||
|
|
||||||
|
return (-1);
|
||||||
|
}
|
22
src/cfg_xmlfile.h
Normal file
22
src/cfg_xmlfile.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 Moritz Grimm <mgrimm@mrsserver.net>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __CFG_XMLFILE_H__
|
||||||
|
#define __CFG_XMLFILE_H__
|
||||||
|
|
||||||
|
int cfg_xmlfile_parse(const char *);
|
||||||
|
|
||||||
|
#endif /* __CFG_XMLFILE_H__ */
|
183
src/cmdline.c
Normal file
183
src/cmdline.c
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 Moritz Grimm <mgrimm@mrsserver.net>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
# include "config.h"
|
||||||
|
#endif /* HAVE_CONFIG_H */
|
||||||
|
|
||||||
|
#include "compat.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#ifdef HAVE_UNISTD_H
|
||||||
|
# include <unistd.h>
|
||||||
|
#endif /* HAVE_UNISTD_H */
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "cfg.h"
|
||||||
|
#include "cmdline.h"
|
||||||
|
#include "playlist.h"
|
||||||
|
|
||||||
|
#define OPTSTRING "c:hqs:Vv"
|
||||||
|
enum opt_vals {
|
||||||
|
OPT_CONFIGFILE = 'c',
|
||||||
|
OPT_HELP = 'h',
|
||||||
|
OPT_QUIETSTDERR = 'q',
|
||||||
|
OPT_SHUFFLEFILE = 's',
|
||||||
|
OPT_VERSION = 'V',
|
||||||
|
OPT_VERBOSE = 'v',
|
||||||
|
OPT_INVALID = '?'
|
||||||
|
};
|
||||||
|
|
||||||
|
static void _usage(void);
|
||||||
|
static void _usage_help(void);
|
||||||
|
static void _set_program_name(const char *);
|
||||||
|
|
||||||
|
static void
|
||||||
|
_usage(void)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "usage: %s [-ghqVv] -c cfgfile\n",
|
||||||
|
cfg_get_program_name());
|
||||||
|
fprintf(stderr, " %s [-ghV] -s file\n",
|
||||||
|
cfg_get_program_name());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
_usage_help(void)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
fprintf(stderr, " -c cfgfile use XML configuration in cfgfile\n");
|
||||||
|
fprintf(stderr, " -h print this help and exit\n");
|
||||||
|
fprintf(stderr, " -q suppress STDERR output from external en-/decoders\n");
|
||||||
|
fprintf(stderr, " -s file read lines from file, shuffle, print to STDOUT, then exit\n");
|
||||||
|
fprintf(stderr, " -V print the version number and exit\n");
|
||||||
|
fprintf(stderr, " -v verbose output (use twice for more effect)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
_set_program_name(const char *argv0)
|
||||||
|
{
|
||||||
|
#ifdef HAVE___PROGNAME
|
||||||
|
extern char *__progname;
|
||||||
|
(void)argv0;
|
||||||
|
cfg_set_program_name(__progname, NULL);
|
||||||
|
#else
|
||||||
|
if (argv0 == NULL) {
|
||||||
|
cfg_set_program_name("ezstream", NULL);
|
||||||
|
} else {
|
||||||
|
const char *p = strrchr(argv0, '/');
|
||||||
|
if (p == NULL)
|
||||||
|
p = argv0;
|
||||||
|
else
|
||||||
|
p++;
|
||||||
|
cfg_set_program_name(p, NULL);
|
||||||
|
}
|
||||||
|
#endif /* HAVE___PROGNAME */
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cmdline_parse(int argc, char *argv[], int *ret_p)
|
||||||
|
{
|
||||||
|
int ch;
|
||||||
|
const char *playlistFile = NULL;
|
||||||
|
unsigned int verbosity = 0;
|
||||||
|
const char *err_str;
|
||||||
|
|
||||||
|
_set_program_name(argv[0]);
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
ch = getopt(argc, argv, OPTSTRING);
|
||||||
|
if (0 > ch)
|
||||||
|
break;
|
||||||
|
|
||||||
|
switch (ch) {
|
||||||
|
case OPT_CONFIGFILE:
|
||||||
|
if (0 > cfg_set_program_config_file(optarg, &err_str) ||
|
||||||
|
0 > cfg_set_program_config_type(CFG_TYPE_XMLFILE, NULL)) {
|
||||||
|
fprintf(stderr, "-%c: argument %s\n",
|
||||||
|
OPT_CONFIGFILE, err_str);
|
||||||
|
_usage();
|
||||||
|
*ret_p = 2;
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OPT_HELP:
|
||||||
|
_usage();
|
||||||
|
_usage_help();
|
||||||
|
*ret_p = 0;
|
||||||
|
return (-1);
|
||||||
|
case OPT_QUIETSTDERR:
|
||||||
|
cfg_set_program_quiet_stderr(1, NULL);
|
||||||
|
break;
|
||||||
|
case OPT_SHUFFLEFILE:
|
||||||
|
playlistFile = optarg;
|
||||||
|
break;
|
||||||
|
case OPT_VERSION:
|
||||||
|
fprintf(stdout, "%s version %s\n",
|
||||||
|
PACKAGE_NAME, PACKAGE_VERSION);
|
||||||
|
*ret_p = 0;
|
||||||
|
return (-1);
|
||||||
|
case OPT_VERBOSE:
|
||||||
|
verbosity++;
|
||||||
|
break;
|
||||||
|
case OPT_INVALID:
|
||||||
|
default:
|
||||||
|
_usage();
|
||||||
|
*ret_p = 2;
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
argc -= optind;
|
||||||
|
argv += optind;
|
||||||
|
|
||||||
|
cfg_set_program_verbosity(verbosity, NULL);
|
||||||
|
|
||||||
|
if (playlistFile) {
|
||||||
|
playlist_t *pl;
|
||||||
|
const char *entry;
|
||||||
|
|
||||||
|
if (0 > playlist_init()) {
|
||||||
|
*ret_p = 1;
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
if (0 == strcmp(playlistFile, "-")) {
|
||||||
|
pl = playlist_read(NULL);
|
||||||
|
} else {
|
||||||
|
pl = playlist_read(playlistFile);
|
||||||
|
}
|
||||||
|
if (pl == NULL) {
|
||||||
|
*ret_p = 1;
|
||||||
|
} else {
|
||||||
|
playlist_shuffle(pl);
|
||||||
|
while (NULL != (entry = playlist_get_next(pl)))
|
||||||
|
printf("%s\n", entry);
|
||||||
|
playlist_free(&pl);
|
||||||
|
*ret_p = 0;
|
||||||
|
}
|
||||||
|
playlist_exit();
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cfg_get_program_config_file()) {
|
||||||
|
fprintf(stderr, "either -%c or -%c must be provided\n",
|
||||||
|
OPT_CONFIGFILE, OPT_SHUFFLEFILE);
|
||||||
|
_usage();
|
||||||
|
*ret_p = 2;
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
22
src/cmdline.h
Normal file
22
src/cmdline.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 Moritz Grimm <mgrimm@mrsserver.net>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __CMDLINE_H__
|
||||||
|
#define __CMDLINE_H__
|
||||||
|
|
||||||
|
int cmdline_parse(int, char *[], int *);
|
||||||
|
|
||||||
|
#endif /* __CMDLINE_H__ */
|
756
src/configfile.c
756
src/configfile.c
@ -1,756 +0,0 @@
|
|||||||
/*
|
|
||||||
* ezstream - source client for Icecast with external en-/decoder support
|
|
||||||
* Copyright (C) 2003, 2004, 2005, 2006 Ed Zaleski <oddsock@oddsock.org>
|
|
||||||
* Copyright (C) 2007, 2009, 2015 Moritz Grimm <mgrimm@mrsserver.net>
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License version 2 as
|
|
||||||
* published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifdef HAVE_CONFIG_H
|
|
||||||
# include "config.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "compat.h"
|
|
||||||
|
|
||||||
#include "ezstream.h"
|
|
||||||
|
|
||||||
#include <libxml/parser.h>
|
|
||||||
|
|
||||||
#include "configfile.h"
|
|
||||||
#include "log.h"
|
|
||||||
#include "util.h"
|
|
||||||
#include "xalloc.h"
|
|
||||||
|
|
||||||
static EZCONFIG ezConfig;
|
|
||||||
static const char *blankString = "";
|
|
||||||
|
|
||||||
unsigned int checkDecoderLine(const char *, const char *, long);
|
|
||||||
unsigned int checkEncoderLine(const char *, const char *, long);
|
|
||||||
unsigned int checkFormatLine(const char *, const char *, long);
|
|
||||||
|
|
||||||
EZCONFIG *
|
|
||||||
getEZConfig(void)
|
|
||||||
{
|
|
||||||
return (&ezConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *
|
|
||||||
getFormatEncoder(const char *format)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0; i < ezConfig.numEncoderDecoders; i++) {
|
|
||||||
if (ezConfig.encoderDecoders[i] != NULL &&
|
|
||||||
ezConfig.encoderDecoders[i]->format != NULL &&
|
|
||||||
strcmp(ezConfig.encoderDecoders[i]->format, format) == 0) {
|
|
||||||
if (ezConfig.encoderDecoders[i]->encoder != NULL)
|
|
||||||
return (ezConfig.encoderDecoders[i]->encoder);
|
|
||||||
else
|
|
||||||
return (blankString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (blankString);
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *
|
|
||||||
getFormatDecoder(const char *match)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0; i < ezConfig.numEncoderDecoders; i++) {
|
|
||||||
if (ezConfig.encoderDecoders[i] != NULL &&
|
|
||||||
ezConfig.encoderDecoders[i]->match != NULL &&
|
|
||||||
strcmp(ezConfig.encoderDecoders[i]->match, match) == 0) {
|
|
||||||
if (ezConfig.encoderDecoders[i]->decoder != NULL)
|
|
||||||
return (ezConfig.encoderDecoders[i]->decoder);
|
|
||||||
else
|
|
||||||
return (blankString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (blankString);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define CFGERROR_TOO_MANY(x) \
|
|
||||||
do { \
|
|
||||||
log_error("%s[%ld]: more than one <%s> element", \
|
|
||||||
fileName, xmlGetLineNo(cur), (x)); \
|
|
||||||
config_error++; \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
int
|
|
||||||
parseConfig(const char *fileName)
|
|
||||||
{
|
|
||||||
xmlDocPtr doc;
|
|
||||||
xmlNodePtr cur;
|
|
||||||
char *ls_xmlContentPtr;
|
|
||||||
int program_set, reconnect_set, shuffle_set,
|
|
||||||
streamOnce_set, svrinfopublic_set,
|
|
||||||
refresh_set;
|
|
||||||
unsigned int config_error;
|
|
||||||
|
|
||||||
xmlLineNumbersDefault(1);
|
|
||||||
if ((doc = xmlParseFile(fileName)) == NULL) {
|
|
||||||
log_error("%s: not well-formed", fileName);
|
|
||||||
return (0);
|
|
||||||
}
|
|
||||||
|
|
||||||
cur = xmlDocGetRootElement(doc);
|
|
||||||
|
|
||||||
if (cur == NULL) {
|
|
||||||
log_error("%s: empty document", fileName);
|
|
||||||
xmlFreeDoc(doc);
|
|
||||||
return (0);
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(&ezConfig, 0, sizeof(ezConfig));
|
|
||||||
ezConfig.metadataRefreshInterval = -1;
|
|
||||||
|
|
||||||
config_error = 0;
|
|
||||||
program_set = 0;
|
|
||||||
reconnect_set = 0;
|
|
||||||
refresh_set = 0;
|
|
||||||
shuffle_set = 0;
|
|
||||||
streamOnce_set = 0;
|
|
||||||
svrinfopublic_set = 0;
|
|
||||||
|
|
||||||
for (cur = cur->xmlChildrenNode; cur != NULL; cur = cur->next) {
|
|
||||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"url")) {
|
|
||||||
if (ezConfig.URL != NULL) {
|
|
||||||
CFGERROR_TOO_MANY("url");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur->xmlChildrenNode != NULL) {
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
|
||||||
ezConfig.URL = xstrdup(ls_xmlContentPtr);
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"sourceuser")) {
|
|
||||||
if (ezConfig.username != NULL) {
|
|
||||||
CFGERROR_TOO_MANY("sourceuser");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur->xmlChildrenNode != NULL) {
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
|
||||||
ezConfig.username = xstrdup(ls_xmlContentPtr);
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"sourcepassword")) {
|
|
||||||
if (ezConfig.password != NULL) {
|
|
||||||
CFGERROR_TOO_MANY("sourcepassword");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur->xmlChildrenNode != NULL) {
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
|
||||||
ezConfig.password = xstrdup(ls_xmlContentPtr);
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"format")) {
|
|
||||||
if (ezConfig.format != NULL) {
|
|
||||||
CFGERROR_TOO_MANY("format");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur->xmlChildrenNode != NULL) {
|
|
||||||
char *p;
|
|
||||||
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
|
||||||
ezConfig.format = xstrdup(ls_xmlContentPtr);
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
for (p = ezConfig.format; *p != '\0'; p++)
|
|
||||||
*p = toupper((int)*p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"filename")) {
|
|
||||||
if (ezConfig.fileName != NULL) {
|
|
||||||
CFGERROR_TOO_MANY("filename");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur->xmlChildrenNode != NULL) {
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
|
||||||
if (strlen(ls_xmlContentPtr) > PATH_MAX - 1) {
|
|
||||||
log_error("%s[%ld]: path or filename in <filename> too long",
|
|
||||||
fileName, xmlGetLineNo(cur));
|
|
||||||
config_error++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ezConfig.fileName = UTF8toCHAR(ls_xmlContentPtr, ICONV_REPLACE);
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"metadata_progname")) {
|
|
||||||
if (ezConfig.metadataProgram != NULL) {
|
|
||||||
CFGERROR_TOO_MANY("metadata_progname");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur->xmlChildrenNode != NULL) {
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
|
||||||
if (strlen(ls_xmlContentPtr) > PATH_MAX - 1) {
|
|
||||||
log_error("%s[%ld]: path or filename in <metadata_progname> too long",
|
|
||||||
fileName, xmlGetLineNo(cur));
|
|
||||||
config_error++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ezConfig.metadataProgram = UTF8toCHAR(ls_xmlContentPtr, ICONV_REPLACE);
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"metadata_format")) {
|
|
||||||
if (ezConfig.metadataFormat != NULL) {
|
|
||||||
CFGERROR_TOO_MANY("metadata_format");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur->xmlChildrenNode != NULL) {
|
|
||||||
unsigned int ret;
|
|
||||||
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
|
||||||
ezConfig.metadataFormat = xstrdup(ls_xmlContentPtr);
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
if ((ret = checkFormatLine(ezConfig.metadataFormat, fileName, xmlGetLineNo(cur)))
|
|
||||||
> 0) {
|
|
||||||
config_error += ret;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"metadata_refreshinterval")) {
|
|
||||||
if (refresh_set) {
|
|
||||||
CFGERROR_TOO_MANY("metadata_refreshinterval");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur->xmlChildrenNode != NULL) {
|
|
||||||
const char *errstr;
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
|
||||||
ezConfig.metadataRefreshInterval = (int)strtonum(ls_xmlContentPtr, -1LL, (long long)INT_MAX, &errstr);
|
|
||||||
if (errstr) {
|
|
||||||
log_error("%s[%ld]: <metadata_refreshinterval>: %s: %s",
|
|
||||||
fileName, xmlGetLineNo(cur), ls_xmlContentPtr, errstr);
|
|
||||||
config_error++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
refresh_set = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"playlist_program")) {
|
|
||||||
if (program_set) {
|
|
||||||
CFGERROR_TOO_MANY("playlist_program");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur->xmlChildrenNode != NULL) {
|
|
||||||
const char *errstr;
|
|
||||||
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
|
||||||
ezConfig.fileNameIsProgram = (int)strtonum(ls_xmlContentPtr, 0LL, 1LL, &errstr);
|
|
||||||
if (errstr) {
|
|
||||||
log_error("%s[%ld]: <playlist_program> may only contain 1 or 0",
|
|
||||||
fileName, xmlGetLineNo(cur));
|
|
||||||
config_error++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
program_set = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"shuffle")) {
|
|
||||||
if (shuffle_set) {
|
|
||||||
CFGERROR_TOO_MANY("shuffle");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur->xmlChildrenNode != NULL) {
|
|
||||||
const char *errstr;
|
|
||||||
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
|
||||||
ezConfig.shuffle = (int)strtonum(ls_xmlContentPtr, 0LL, 1LL, &errstr);
|
|
||||||
if (errstr) {
|
|
||||||
log_error("%s[%ld]: <shuffle> may only contain 1 or 0",
|
|
||||||
fileName, xmlGetLineNo(cur));
|
|
||||||
config_error++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
shuffle_set = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"stream_once")) {
|
|
||||||
if (streamOnce_set) {
|
|
||||||
CFGERROR_TOO_MANY("stream_once");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur->xmlChildrenNode != NULL) {
|
|
||||||
const char *errstr;
|
|
||||||
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
|
||||||
ezConfig.streamOnce = (int)strtonum(ls_xmlContentPtr, 0LL, 1LL, &errstr);
|
|
||||||
if (errstr) {
|
|
||||||
log_error("%s[%ld]: <stream_once> may only contain 1 or 0",
|
|
||||||
fileName, xmlGetLineNo(cur));
|
|
||||||
config_error++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
streamOnce_set = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"reconnect_tries")) {
|
|
||||||
if (reconnect_set) {
|
|
||||||
CFGERROR_TOO_MANY("reconnect_tries");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur->xmlChildrenNode != NULL) {
|
|
||||||
const char *errstr;
|
|
||||||
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
|
||||||
ezConfig.reconnectAttempts = (unsigned int)strtonum(ls_xmlContentPtr, 0LL, (long long)UINT_MAX, &errstr);
|
|
||||||
if (errstr) {
|
|
||||||
log_error("%s[%ld]: <reconnect_tries>: %s: %s",
|
|
||||||
fileName, xmlGetLineNo(cur), ls_xmlContentPtr, errstr);
|
|
||||||
config_error++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
reconnect_set = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"svrinfoname")) {
|
|
||||||
if (ezConfig.serverName != NULL) {
|
|
||||||
CFGERROR_TOO_MANY("svrinfoname");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur->xmlChildrenNode != NULL) {
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
|
||||||
ezConfig.serverName = xstrdup(ls_xmlContentPtr);
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"svrinfourl")) {
|
|
||||||
if (ezConfig.serverURL != NULL) {
|
|
||||||
CFGERROR_TOO_MANY("svrinfourl");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur->xmlChildrenNode != NULL) {
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
|
||||||
ezConfig.serverURL = xstrdup(ls_xmlContentPtr);
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"svrinfogenre")) {
|
|
||||||
if (ezConfig.serverGenre != NULL) {
|
|
||||||
CFGERROR_TOO_MANY("svrinfogenre");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur->xmlChildrenNode != NULL) {
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
|
||||||
ezConfig.serverGenre = xstrdup(ls_xmlContentPtr);
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"svrinfodescription")) {
|
|
||||||
if (ezConfig.serverDescription != NULL) {
|
|
||||||
CFGERROR_TOO_MANY("svrinfodescription");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur->xmlChildrenNode != NULL) {
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
|
||||||
ezConfig.serverDescription = xstrdup(ls_xmlContentPtr);
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"svrinfobitrate")) {
|
|
||||||
if (ezConfig.serverBitrate != NULL) {
|
|
||||||
CFGERROR_TOO_MANY("svrinfobitrate");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur->xmlChildrenNode != NULL) {
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
|
||||||
ezConfig.serverBitrate = xstrdup(ls_xmlContentPtr);
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"svrinfochannels")) {
|
|
||||||
if (ezConfig.serverChannels != NULL) {
|
|
||||||
CFGERROR_TOO_MANY("svrinfochannels");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur->xmlChildrenNode != NULL) {
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
|
||||||
ezConfig.serverChannels = xstrdup(ls_xmlContentPtr);
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"svrinfosamplerate")) {
|
|
||||||
if (ezConfig.serverSamplerate != NULL) {
|
|
||||||
CFGERROR_TOO_MANY("svrinfosamplerate");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur->xmlChildrenNode != NULL) {
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
|
||||||
ezConfig.serverSamplerate = xstrdup(ls_xmlContentPtr);
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"svrinfoquality")) {
|
|
||||||
if (ezConfig.serverQuality != NULL) {
|
|
||||||
CFGERROR_TOO_MANY("svrinfoquality");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur->xmlChildrenNode != NULL) {
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
|
||||||
ezConfig.serverQuality = xstrdup(ls_xmlContentPtr);
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"svrinfopublic")) {
|
|
||||||
if (svrinfopublic_set) {
|
|
||||||
CFGERROR_TOO_MANY("svrinfopublic");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur->xmlChildrenNode != NULL) {
|
|
||||||
const char *errstr;
|
|
||||||
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
|
|
||||||
ezConfig.serverPublic = (int)strtonum(ls_xmlContentPtr, 0LL, 1LL, &errstr);
|
|
||||||
if (errstr) {
|
|
||||||
log_error("%s[%ld]: <svrinfopublic> may only contain 1 or 0",
|
|
||||||
fileName, xmlGetLineNo(cur));
|
|
||||||
config_error++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
svrinfopublic_set = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!xmlStrcmp(cur->name, (const xmlChar *)"reencode")) {
|
|
||||||
xmlNodePtr cur2;
|
|
||||||
int enable_set;
|
|
||||||
|
|
||||||
enable_set = 0;
|
|
||||||
for (cur2 = cur->xmlChildrenNode; cur2 != NULL;
|
|
||||||
cur2 = cur2->next) {
|
|
||||||
if (!xmlStrcmp(cur2->name, (const xmlChar *)"enable")) {
|
|
||||||
if (enable_set) {
|
|
||||||
CFGERROR_TOO_MANY("enable");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur2->xmlChildrenNode != NULL) {
|
|
||||||
const char *errstr;
|
|
||||||
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur2->xmlChildrenNode, 1);
|
|
||||||
ezConfig.reencode = (int)strtonum(ls_xmlContentPtr, 0LL, 1LL, &errstr);
|
|
||||||
if (errstr) {
|
|
||||||
log_error("%s[%ld]: <enable> may only contain 1 or 0",
|
|
||||||
fileName, xmlGetLineNo(cur));
|
|
||||||
config_error++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
enable_set = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!xmlStrcmp(cur2->name, (const xmlChar *)"encdec")) {
|
|
||||||
xmlNodePtr cur3;
|
|
||||||
FORMAT_ENCDEC *pformatEncDec;
|
|
||||||
|
|
||||||
pformatEncDec = xcalloc(1UL, sizeof(FORMAT_ENCDEC));
|
|
||||||
|
|
||||||
for (cur3 = cur2->xmlChildrenNode;
|
|
||||||
cur3 != NULL; cur3 = cur3->next) {
|
|
||||||
if (!xmlStrcmp(cur3->name, (const xmlChar *)"format")) {
|
|
||||||
if (pformatEncDec->format != NULL) {
|
|
||||||
CFGERROR_TOO_MANY("format");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur3->xmlChildrenNode != NULL) {
|
|
||||||
char *p;
|
|
||||||
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur3->xmlChildrenNode, 1);
|
|
||||||
pformatEncDec->format = xstrdup(ls_xmlContentPtr);
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
for (p = pformatEncDec->format; *p != '\0'; p++)
|
|
||||||
*p = toupper((int)*p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!xmlStrcmp(cur3->name, (const xmlChar *)"match")) {
|
|
||||||
if (pformatEncDec->match != NULL) {
|
|
||||||
CFGERROR_TOO_MANY("match");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur3->xmlChildrenNode != NULL) {
|
|
||||||
char *p;
|
|
||||||
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur3->xmlChildrenNode, 1);
|
|
||||||
pformatEncDec->match = xstrdup(ls_xmlContentPtr);
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
for (p = pformatEncDec->match; *p != '\0'; p++)
|
|
||||||
*p = tolower((int)*p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!xmlStrcmp(cur3->name, (const xmlChar *)"decode")) {
|
|
||||||
if (pformatEncDec->decoder != NULL) {
|
|
||||||
CFGERROR_TOO_MANY("decode");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur3->xmlChildrenNode != NULL) {
|
|
||||||
unsigned int ret;
|
|
||||||
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur3->xmlChildrenNode, 1);
|
|
||||||
pformatEncDec->decoder = UTF8toCHAR(ls_xmlContentPtr, ICONV_REPLACE);
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
if ((ret = checkDecoderLine(pformatEncDec->decoder, fileName, xmlGetLineNo(cur3)))
|
|
||||||
> 0) {
|
|
||||||
config_error += ret;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!xmlStrcmp(cur3->name, (const xmlChar *)"encode")) {
|
|
||||||
if (pformatEncDec->encoder != NULL) {
|
|
||||||
CFGERROR_TOO_MANY("encode");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cur3->xmlChildrenNode != NULL) {
|
|
||||||
unsigned int ret;
|
|
||||||
|
|
||||||
ls_xmlContentPtr = (char *)xmlNodeListGetString(doc, cur3->xmlChildrenNode, 1);
|
|
||||||
pformatEncDec->encoder = UTF8toCHAR(ls_xmlContentPtr, ICONV_REPLACE);
|
|
||||||
xmlFree(ls_xmlContentPtr);
|
|
||||||
if ((ret = checkEncoderLine(pformatEncDec->encoder, fileName, xmlGetLineNo(cur3)))
|
|
||||||
> 0) {
|
|
||||||
config_error += ret;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ezConfig.encoderDecoders[ezConfig.numEncoderDecoders] = pformatEncDec;
|
|
||||||
ezConfig.numEncoderDecoders++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
xmlFreeDoc(doc);
|
|
||||||
|
|
||||||
if (config_error == 0)
|
|
||||||
return (1);
|
|
||||||
|
|
||||||
freeConfig(&ezConfig);
|
|
||||||
log_error("%s: %u configuration error(s)", fileName, config_error);
|
|
||||||
|
|
||||||
return (0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
freeConfig(EZCONFIG *cfg)
|
|
||||||
{
|
|
||||||
unsigned int i;
|
|
||||||
|
|
||||||
if (cfg == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (cfg->URL != NULL)
|
|
||||||
xfree(cfg->URL);
|
|
||||||
if (cfg->password != NULL)
|
|
||||||
xfree(cfg->password);
|
|
||||||
if (cfg->format != NULL)
|
|
||||||
xfree(cfg->format);
|
|
||||||
if (cfg->fileName != NULL)
|
|
||||||
xfree(cfg->fileName);
|
|
||||||
if (cfg->metadataProgram != NULL)
|
|
||||||
xfree(cfg->metadataProgram);
|
|
||||||
if (cfg->metadataFormat != NULL)
|
|
||||||
xfree(cfg->metadataFormat);
|
|
||||||
if (cfg->serverName != NULL)
|
|
||||||
xfree(cfg->serverName);
|
|
||||||
if (cfg->serverURL != NULL)
|
|
||||||
xfree(cfg->serverURL);
|
|
||||||
if (cfg->serverGenre != NULL)
|
|
||||||
xfree(cfg->serverGenre);
|
|
||||||
if (cfg->serverDescription != NULL)
|
|
||||||
xfree(cfg->serverDescription);
|
|
||||||
if (cfg->serverBitrate != NULL)
|
|
||||||
xfree(cfg->serverBitrate);
|
|
||||||
if (cfg->serverChannels != NULL)
|
|
||||||
xfree(cfg->serverChannels);
|
|
||||||
if (cfg->serverSamplerate != NULL)
|
|
||||||
xfree(cfg->serverSamplerate);
|
|
||||||
if (cfg->serverQuality != NULL)
|
|
||||||
xfree(cfg->serverQuality);
|
|
||||||
if (cfg->encoderDecoders != NULL) {
|
|
||||||
for (i = 0; i < MAX_FORMAT_ENCDEC; i++) {
|
|
||||||
if (cfg->encoderDecoders[i] != NULL) {
|
|
||||||
if (cfg->encoderDecoders[i]->format != NULL)
|
|
||||||
xfree(cfg->encoderDecoders[i]->format);
|
|
||||||
if (cfg->encoderDecoders[i]->match != NULL)
|
|
||||||
xfree(cfg->encoderDecoders[i]->match);
|
|
||||||
if (cfg->encoderDecoders[i]->encoder != NULL)
|
|
||||||
xfree(cfg->encoderDecoders[i]->encoder);
|
|
||||||
if (cfg->encoderDecoders[i]->decoder != NULL)
|
|
||||||
xfree(cfg->encoderDecoders[i]->decoder);
|
|
||||||
xfree(cfg->encoderDecoders[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(cfg, 0, sizeof(EZCONFIG));
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int
|
|
||||||
checkDecoderLine(const char *str, const char *file, long line)
|
|
||||||
{
|
|
||||||
unsigned int errors;
|
|
||||||
char *p;
|
|
||||||
int have_track = 0;
|
|
||||||
|
|
||||||
errors = 0;
|
|
||||||
if ((p = strstr(str, STRING_PLACEHOLDER)) != NULL) {
|
|
||||||
log_error("%s[%ld]: '%s' placeholder not allowed in decoder command",
|
|
||||||
file, line, STRING_PLACEHOLDER);
|
|
||||||
errors++;
|
|
||||||
}
|
|
||||||
if ((p = strstr(str, TRACK_PLACEHOLDER)) != NULL) {
|
|
||||||
p += strlen(TRACK_PLACEHOLDER);
|
|
||||||
if ((p = strstr(p, TRACK_PLACEHOLDER)) != NULL) {
|
|
||||||
log_error("%s[%ld]: more than one '%s' placeholder in decoder command",
|
|
||||||
file, line, TRACK_PLACEHOLDER);
|
|
||||||
errors++;
|
|
||||||
} else
|
|
||||||
have_track = 1;
|
|
||||||
}
|
|
||||||
if ((p = strstr(str, METADATA_PLACEHOLDER)) != NULL) {
|
|
||||||
p += strlen(METADATA_PLACEHOLDER);
|
|
||||||
if ((p = strstr(p, METADATA_PLACEHOLDER)) != NULL) {
|
|
||||||
log_error("%s[%ld]: more than one '%s' placeholder in decoder command",
|
|
||||||
file, line, METADATA_PLACEHOLDER);
|
|
||||||
errors++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((p = strstr(str, ARTIST_PLACEHOLDER)) != NULL) {
|
|
||||||
p += strlen(ARTIST_PLACEHOLDER);
|
|
||||||
if ((p = strstr(p, ARTIST_PLACEHOLDER)) != NULL) {
|
|
||||||
log_error("%s[%ld]: more than one '%s' placeholder in decoder command",
|
|
||||||
file, line, ARTIST_PLACEHOLDER);
|
|
||||||
errors++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((p = strstr(str, TITLE_PLACEHOLDER)) != NULL) {
|
|
||||||
p += strlen(TITLE_PLACEHOLDER);
|
|
||||||
if ((p = strstr(p, TITLE_PLACEHOLDER)) != NULL) {
|
|
||||||
log_error("%s[%ld]: more than one '%s' placeholder in decoder command",
|
|
||||||
file, line, TITLE_PLACEHOLDER);
|
|
||||||
errors++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!have_track) {
|
|
||||||
log_error("%s[%ld]: decoder command requires '%s' track placeholder",
|
|
||||||
file, line, TRACK_PLACEHOLDER);
|
|
||||||
errors++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int
|
|
||||||
checkEncoderLine(const char *str, const char *file, long line)
|
|
||||||
{
|
|
||||||
unsigned int errors;
|
|
||||||
char *p;
|
|
||||||
|
|
||||||
errors = 0;
|
|
||||||
if ((p = strstr(str, TRACK_PLACEHOLDER)) != NULL) {
|
|
||||||
log_error("%s[%ld]: '%s' placeholder not allowed in encoder command",
|
|
||||||
file, line, TRACK_PLACEHOLDER);
|
|
||||||
errors++;
|
|
||||||
}
|
|
||||||
if ((p = strstr(str, STRING_PLACEHOLDER)) != NULL) {
|
|
||||||
log_error("%s[%ld]: '%s' placeholder not allowed in encoder command",
|
|
||||||
file, line, STRING_PLACEHOLDER);
|
|
||||||
errors++;
|
|
||||||
}
|
|
||||||
if ((p = strstr(str, METADATA_PLACEHOLDER)) != NULL) {
|
|
||||||
p += strlen(METADATA_PLACEHOLDER);
|
|
||||||
if ((p = strstr(p, METADATA_PLACEHOLDER)) != NULL) {
|
|
||||||
log_error("%s[%ld]: more than one '%s' placeholder in encoder command",
|
|
||||||
file, line, METADATA_PLACEHOLDER);
|
|
||||||
errors++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((p = strstr(str, ARTIST_PLACEHOLDER)) != NULL) {
|
|
||||||
p += strlen(ARTIST_PLACEHOLDER);
|
|
||||||
if ((p = strstr(p, ARTIST_PLACEHOLDER)) != NULL) {
|
|
||||||
log_error("%s[%ld]: more than one '%s' placeholder in encoder command",
|
|
||||||
file, line, ARTIST_PLACEHOLDER);
|
|
||||||
errors++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((p = strstr(str, TITLE_PLACEHOLDER)) != NULL) {
|
|
||||||
p += strlen(TITLE_PLACEHOLDER);
|
|
||||||
if ((p = strstr(p, TITLE_PLACEHOLDER)) != NULL) {
|
|
||||||
log_error("%s[%ld]: more than one '%s' placeholder in encoder command",
|
|
||||||
file, line, TITLE_PLACEHOLDER);
|
|
||||||
errors++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int
|
|
||||||
checkFormatLine(const char *str, const char *file, long line)
|
|
||||||
{
|
|
||||||
unsigned int errors;
|
|
||||||
char *p;
|
|
||||||
|
|
||||||
errors = 0;
|
|
||||||
if ((p = strstr(str, METADATA_PLACEHOLDER)) != NULL) {
|
|
||||||
log_error("%s[%ld]: '%s' placeholder not allowed in <metadata_format>",
|
|
||||||
file, line, METADATA_PLACEHOLDER);
|
|
||||||
errors++;
|
|
||||||
}
|
|
||||||
if ((p = strstr(str, TRACK_PLACEHOLDER)) != NULL) {
|
|
||||||
p += strlen(TRACK_PLACEHOLDER);
|
|
||||||
if ((p = strstr(p, TRACK_PLACEHOLDER)) != NULL) {
|
|
||||||
log_error("%s[%ld]: more than one '%s' placeholder in <metadata_format>",
|
|
||||||
file, line, TRACK_PLACEHOLDER);
|
|
||||||
errors++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((p = strstr(str, STRING_PLACEHOLDER)) != NULL) {
|
|
||||||
p += strlen(STRING_PLACEHOLDER);
|
|
||||||
if ((p = strstr(p, STRING_PLACEHOLDER)) != NULL) {
|
|
||||||
log_error("%s[%ld]: more than one '%s' placeholder in <metadata_format>",
|
|
||||||
file, line, STRING_PLACEHOLDER);
|
|
||||||
errors++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((p = strstr(str, ARTIST_PLACEHOLDER)) != NULL) {
|
|
||||||
p += strlen(ARTIST_PLACEHOLDER);
|
|
||||||
if ((p = strstr(p, ARTIST_PLACEHOLDER)) != NULL) {
|
|
||||||
log_error("%s[%ld]: more than one '%s' placeholder in <metadata_format>",
|
|
||||||
file, line, ARTIST_PLACEHOLDER);
|
|
||||||
errors++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((p = strstr(str, TITLE_PLACEHOLDER)) != NULL) {
|
|
||||||
p += strlen(TITLE_PLACEHOLDER);
|
|
||||||
if ((p = strstr(p, TITLE_PLACEHOLDER)) != NULL) {
|
|
||||||
log_error("%s[%ld]: more than one '%s' placeholder in <metadata_format>",
|
|
||||||
file, line, TITLE_PLACEHOLDER);
|
|
||||||
errors++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (errors);
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
/*
|
|
||||||
* ezstream - source client for Icecast with external en-/decoder support
|
|
||||||
* Copyright (C) 2003, 2004, 2005, 2006 Ed Zaleski <oddsock@oddsock.org>
|
|
||||||
* Copyright (C) 2007, 2015 Moritz Grimm <mgrimm@mrsserver.net>
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License version 2 as
|
|
||||||
* published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef __CONFIGFILE_H__
|
|
||||||
#define __CONFIGFILE_H__
|
|
||||||
|
|
||||||
#define MP3_FORMAT "MP3"
|
|
||||||
#define VORBIS_FORMAT "VORBIS"
|
|
||||||
#define THEORA_FORMAT "THEORA"
|
|
||||||
|
|
||||||
#define MAX_FORMAT_ENCDEC 15
|
|
||||||
|
|
||||||
#define TRACK_PLACEHOLDER "@T@"
|
|
||||||
#define METADATA_PLACEHOLDER "@M@"
|
|
||||||
#define ARTIST_PLACEHOLDER "@a@"
|
|
||||||
#define TITLE_PLACEHOLDER "@t@"
|
|
||||||
#define STRING_PLACEHOLDER "@s@"
|
|
||||||
|
|
||||||
typedef struct tag_FORMAT_ENCDEC {
|
|
||||||
char *format;
|
|
||||||
char *match;
|
|
||||||
char *encoder;
|
|
||||||
char *decoder;
|
|
||||||
} FORMAT_ENCDEC;
|
|
||||||
|
|
||||||
typedef struct tag_EZCONFIG {
|
|
||||||
char *URL;
|
|
||||||
char *username;
|
|
||||||
char *password;
|
|
||||||
char *format;
|
|
||||||
char *fileName;
|
|
||||||
char *metadataProgram;
|
|
||||||
char *metadataFormat;
|
|
||||||
char *serverName;
|
|
||||||
char *serverURL;
|
|
||||||
char *serverGenre;
|
|
||||||
char *serverDescription;
|
|
||||||
char *serverBitrate;
|
|
||||||
char *serverChannels;
|
|
||||||
char *serverSamplerate;
|
|
||||||
char *serverQuality;
|
|
||||||
int serverPublic;
|
|
||||||
int reencode;
|
|
||||||
FORMAT_ENCDEC *encoderDecoders[MAX_FORMAT_ENCDEC];
|
|
||||||
int numEncoderDecoders;
|
|
||||||
int shuffle;
|
|
||||||
int fileNameIsProgram;
|
|
||||||
int streamOnce;
|
|
||||||
unsigned int reconnectAttempts;
|
|
||||||
int metadataRefreshInterval;
|
|
||||||
} EZCONFIG;
|
|
||||||
|
|
||||||
EZCONFIG * getEZConfig(void);
|
|
||||||
const char * getFormatEncoder(const char *format);
|
|
||||||
const char * getFormatDecoder(const char *match);
|
|
||||||
int parseConfig(const char *fileName);
|
|
||||||
void freeConfig(EZCONFIG *);
|
|
||||||
|
|
||||||
#endif /* __CONFIGFILE_H__ */
|
|
426
src/ezstream.c
426
src/ezstream.c
@ -28,7 +28,7 @@
|
|||||||
#include <shout/shout.h>
|
#include <shout/shout.h>
|
||||||
|
|
||||||
#include "cfg.h"
|
#include "cfg.h"
|
||||||
#include "configfile.h"
|
#include "cmdline.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "metadata.h"
|
#include "metadata.h"
|
||||||
#include "playlist.h"
|
#include "playlist.h"
|
||||||
@ -41,9 +41,6 @@
|
|||||||
#define STREAM_SERVERR 3
|
#define STREAM_SERVERR 3
|
||||||
#define STREAM_UPDMDATA 4
|
#define STREAM_UPDMDATA 4
|
||||||
|
|
||||||
int metadataFromProgram;
|
|
||||||
|
|
||||||
EZCONFIG *pezConfig = NULL;
|
|
||||||
playlist_t *playlist = NULL;
|
playlist_t *playlist = NULL;
|
||||||
int playlistMode = 0;
|
int playlistMode = 0;
|
||||||
unsigned int resource_errors = 0;
|
unsigned int resource_errors = 0;
|
||||||
@ -79,7 +76,7 @@ typedef struct tag_ID3Tag {
|
|||||||
int urlParse(const char *, char **, unsigned short *, char **);
|
int urlParse(const char *, char **, unsigned short *, char **);
|
||||||
char * shellQuote(const char *);
|
char * shellQuote(const char *);
|
||||||
char * replaceString(const char *, const char *, const char *);
|
char * replaceString(const char *, const char *, const char *);
|
||||||
char * buildCommandString(const char *, const char *, metadata_t *);
|
char * buildReencodeCommand(const char *, const char *, metadata_t *);
|
||||||
char * getMetadataString(const char *, metadata_t *);
|
char * getMetadataString(const char *, metadata_t *);
|
||||||
metadata_t * getMetadata(const char *);
|
metadata_t * getMetadata(const char *);
|
||||||
int setMetadata(shout_t *, metadata_t *, char **);
|
int setMetadata(shout_t *, metadata_t *, char **);
|
||||||
@ -90,7 +87,7 @@ const char * getTimeString(long);
|
|||||||
int sendStream(shout_t *, FILE *, const char *, int, const char *,
|
int sendStream(shout_t *, FILE *, const char *, int, const char *,
|
||||||
struct timeval *);
|
struct timeval *);
|
||||||
int streamFile(shout_t *, const char *);
|
int streamFile(shout_t *, const char *);
|
||||||
int streamPlaylist(shout_t *, const char *);
|
int streamPlaylist(shout_t *);
|
||||||
int ez_shutdown(int);
|
int ez_shutdown(int);
|
||||||
|
|
||||||
#ifdef HAVE_SIGNALS
|
#ifdef HAVE_SIGNALS
|
||||||
@ -239,44 +236,47 @@ replaceString(const char *source, const char *from, const char *to)
|
|||||||
}
|
}
|
||||||
|
|
||||||
char *
|
char *
|
||||||
buildCommandString(const char *extension, const char *fileName,
|
buildReencodeCommand(const char *extension, const char *fileName,
|
||||||
metadata_t *mdata)
|
metadata_t *mdata)
|
||||||
{
|
{
|
||||||
char *commandString = NULL;
|
cfg_decoder_t decoder;
|
||||||
size_t commandStringLen = 0;
|
cfg_encoder_t encoder;
|
||||||
char *encoder = NULL;
|
char *dec_str, *enc_str;
|
||||||
char *decoder = NULL;
|
char *commandString;
|
||||||
char *newDecoder = NULL;
|
size_t commandStringLen;
|
||||||
char *newEncoder = NULL;
|
char *localTitle, *localArtist, *localMetaString;
|
||||||
char *localTitle = UTF8toCHAR(metadata_get_title(mdata),
|
|
||||||
ICONV_REPLACE);
|
|
||||||
char *localArtist = UTF8toCHAR(metadata_get_artist(mdata),
|
|
||||||
ICONV_REPLACE);
|
|
||||||
char *localMetaString = UTF8toCHAR(metadata_get_string(mdata),
|
|
||||||
ICONV_REPLACE);
|
|
||||||
|
|
||||||
decoder = xstrdup(getFormatDecoder(extension));
|
decoder = cfg_decoder_find(extension);
|
||||||
if (strlen(decoder) == 0) {
|
if (!decoder) {
|
||||||
log_error("cannot decode: %s: unknown file extension %s",
|
log_error("cannot decode: %s: unsupported file extension %s",
|
||||||
fileName, extension);
|
fileName, extension);
|
||||||
xfree(localTitle);
|
|
||||||
xfree(localArtist);
|
|
||||||
xfree(localMetaString);
|
|
||||||
xfree(decoder);
|
|
||||||
return (NULL);
|
return (NULL);
|
||||||
}
|
}
|
||||||
newDecoder = replaceString(decoder, TRACK_PLACEHOLDER, fileName);
|
encoder = cfg_encoder_get(cfg_get_stream_encoder());
|
||||||
if (strstr(decoder, ARTIST_PLACEHOLDER) != NULL) {
|
if (!encoder) {
|
||||||
char *tmpStr = replaceString(newDecoder, ARTIST_PLACEHOLDER,
|
log_error("cannot encode: %s: unknown encoder",
|
||||||
localArtist);
|
cfg_get_stream_encoder());
|
||||||
xfree(newDecoder);
|
return (NULL);
|
||||||
newDecoder = tmpStr;
|
|
||||||
}
|
}
|
||||||
if (strstr(decoder, TITLE_PLACEHOLDER) != NULL) {
|
|
||||||
char *tmpStr = replaceString(newDecoder, TITLE_PLACEHOLDER,
|
localTitle = UTF8toCHAR(metadata_get_title(mdata), ICONV_REPLACE);
|
||||||
|
localArtist = UTF8toCHAR(metadata_get_artist(mdata), ICONV_REPLACE);
|
||||||
|
localMetaString = UTF8toCHAR(metadata_get_string(mdata),
|
||||||
|
ICONV_REPLACE);
|
||||||
|
|
||||||
|
dec_str = replaceString(cfg_decoder_get_program(decoder),
|
||||||
|
PLACEHOLDER_TRACK, fileName);
|
||||||
|
if (strstr(dec_str, PLACEHOLDER_ARTIST) != NULL) {
|
||||||
|
char *tmpStr = replaceString(dec_str, PLACEHOLDER_ARTIST,
|
||||||
|
localArtist);
|
||||||
|
xfree(dec_str);
|
||||||
|
dec_str = tmpStr;
|
||||||
|
}
|
||||||
|
if (strstr(dec_str, PLACEHOLDER_TITLE) != NULL) {
|
||||||
|
char *tmpStr = replaceString(dec_str, PLACEHOLDER_TITLE,
|
||||||
localTitle);
|
localTitle);
|
||||||
xfree(newDecoder);
|
xfree(dec_str);
|
||||||
newDecoder = tmpStr;
|
dec_str = tmpStr;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* if meta
|
* if meta
|
||||||
@ -288,89 +288,77 @@ buildCommandString(const char *extension, const char *fileName,
|
|||||||
* else
|
* else
|
||||||
* replacemeta
|
* replacemeta
|
||||||
*/
|
*/
|
||||||
if (strstr(decoder, METADATA_PLACEHOLDER) != NULL) {
|
if (strstr(dec_str, PLACEHOLDER_METADATA) != NULL) {
|
||||||
if (metadataFromProgram && pezConfig->metadataFormat != NULL) {
|
if (cfg_get_metadata_program() &&
|
||||||
char *mdataString = getMetadataString(pezConfig->metadataFormat, mdata);
|
cfg_get_metadata_format_str()) {
|
||||||
char *tmpStr = replaceString(newDecoder,
|
char *mdataString = getMetadataString(cfg_get_metadata_format_str(),
|
||||||
METADATA_PLACEHOLDER, mdataString);
|
mdata);
|
||||||
xfree(newDecoder);
|
char *tmpStr = replaceString(dec_str,
|
||||||
|
PLACEHOLDER_METADATA, mdataString);
|
||||||
|
xfree(dec_str);
|
||||||
xfree(mdataString);
|
xfree(mdataString);
|
||||||
newDecoder = tmpStr;
|
dec_str = tmpStr;
|
||||||
} else {
|
} else {
|
||||||
if (!metadataFromProgram && strstr(decoder, TITLE_PLACEHOLDER) != NULL) {
|
if (!cfg_get_metadata_program() &&
|
||||||
char *tmpStr = replaceString(newDecoder,
|
strstr(dec_str, PLACEHOLDER_TITLE) != NULL) {
|
||||||
METADATA_PLACEHOLDER, "");
|
char *tmpStr = replaceString(dec_str,
|
||||||
xfree(newDecoder);
|
PLACEHOLDER_METADATA, "");
|
||||||
newDecoder = tmpStr;
|
xfree(dec_str);
|
||||||
|
dec_str = tmpStr;
|
||||||
} else {
|
} else {
|
||||||
char *tmpStr = replaceString(newDecoder,
|
char *tmpStr = replaceString(dec_str,
|
||||||
METADATA_PLACEHOLDER, localMetaString);
|
PLACEHOLDER_METADATA, localMetaString);
|
||||||
xfree(newDecoder);
|
xfree(dec_str);
|
||||||
newDecoder = tmpStr;
|
dec_str = tmpStr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
encoder = xstrdup(getFormatEncoder(pezConfig->format));
|
enc_str = replaceString(cfg_encoder_get_program(encoder),
|
||||||
if (strlen(encoder) == 0) {
|
PLACEHOLDER_ARTIST, localArtist);
|
||||||
log_notice("passing through%s%s data from the decoder",
|
if (strstr(enc_str, PLACEHOLDER_TITLE) != NULL) {
|
||||||
(strcmp(pezConfig->format, THEORA_FORMAT) != 0) ? " (unsupported) " : " ",
|
char *tmpStr = replaceString(enc_str, PLACEHOLDER_TITLE,
|
||||||
pezConfig->format);
|
|
||||||
commandStringLen = strlen(newDecoder) + 1;
|
|
||||||
commandString = xcalloc(commandStringLen, sizeof(char));
|
|
||||||
strlcpy(commandString, newDecoder, commandStringLen);
|
|
||||||
xfree(localTitle);
|
|
||||||
xfree(localArtist);
|
|
||||||
xfree(localMetaString);
|
|
||||||
xfree(decoder);
|
|
||||||
xfree(encoder);
|
|
||||||
xfree(newDecoder);
|
|
||||||
return (commandString);
|
|
||||||
}
|
|
||||||
|
|
||||||
newEncoder = replaceString(encoder, ARTIST_PLACEHOLDER, localArtist);
|
|
||||||
if (strstr(encoder, TITLE_PLACEHOLDER) != NULL) {
|
|
||||||
char *tmpStr = replaceString(newEncoder, TITLE_PLACEHOLDER,
|
|
||||||
localTitle);
|
localTitle);
|
||||||
xfree(newEncoder);
|
xfree(enc_str);
|
||||||
newEncoder = tmpStr;
|
enc_str = tmpStr;
|
||||||
}
|
}
|
||||||
if (strstr(encoder, METADATA_PLACEHOLDER) != NULL) {
|
if (strstr(enc_str, PLACEHOLDER_METADATA) != NULL) {
|
||||||
if (metadataFromProgram && pezConfig->metadataFormat != NULL) {
|
if (cfg_get_metadata_program() &&
|
||||||
char *mdataString = getMetadataString(pezConfig->metadataFormat, mdata);
|
cfg_get_metadata_format_str()) {
|
||||||
char *tmpStr = replaceString(newEncoder,
|
char *mdataString = getMetadataString(cfg_get_metadata_format_str(),
|
||||||
METADATA_PLACEHOLDER, mdataString);
|
mdata);
|
||||||
xfree(newEncoder);
|
char *tmpStr = replaceString(enc_str,
|
||||||
|
PLACEHOLDER_METADATA, mdataString);
|
||||||
|
xfree(enc_str);
|
||||||
xfree(mdataString);
|
xfree(mdataString);
|
||||||
newEncoder = tmpStr;
|
enc_str = tmpStr;
|
||||||
} else {
|
} else {
|
||||||
if (!metadataFromProgram && strstr(encoder, TITLE_PLACEHOLDER) != NULL) {
|
if (!cfg_get_metadata_program() &&
|
||||||
char *tmpStr = replaceString(newEncoder,
|
strstr(enc_str, PLACEHOLDER_TITLE) != NULL) {
|
||||||
METADATA_PLACEHOLDER, "");
|
char *tmpStr = replaceString(enc_str,
|
||||||
xfree(newEncoder);
|
PLACEHOLDER_METADATA, "");
|
||||||
newEncoder = tmpStr;
|
xfree(enc_str);
|
||||||
|
enc_str = tmpStr;
|
||||||
} else {
|
} else {
|
||||||
char *tmpStr = replaceString(newEncoder,
|
char *tmpStr = replaceString(enc_str,
|
||||||
METADATA_PLACEHOLDER, localMetaString);
|
PLACEHOLDER_METADATA, localMetaString);
|
||||||
xfree(newEncoder);
|
xfree(enc_str);
|
||||||
newEncoder = tmpStr;
|
enc_str = tmpStr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
commandStringLen = strlen(newDecoder) + strlen(" | ") +
|
commandStringLen = strlen(dec_str) + strlen(" | ") +
|
||||||
strlen(newEncoder) + 1;
|
strlen(enc_str) + 1;
|
||||||
commandString = xcalloc(commandStringLen, sizeof(char));
|
commandString = xcalloc(commandStringLen, sizeof(char));
|
||||||
snprintf(commandString, commandStringLen, "%s | %s", newDecoder,
|
snprintf(commandString, commandStringLen, "%s | %s", dec_str,
|
||||||
newEncoder);
|
enc_str);
|
||||||
|
|
||||||
xfree(localTitle);
|
xfree(localTitle);
|
||||||
xfree(localArtist);
|
xfree(localArtist);
|
||||||
xfree(localMetaString);
|
xfree(localMetaString);
|
||||||
xfree(decoder);
|
xfree(dec_str);
|
||||||
xfree(encoder);
|
xfree(enc_str);
|
||||||
xfree(newDecoder);
|
|
||||||
xfree(newEncoder);
|
|
||||||
|
|
||||||
return (commandString);
|
return (commandString);
|
||||||
}
|
}
|
||||||
@ -385,26 +373,26 @@ getMetadataString(const char *format, metadata_t *mdata)
|
|||||||
|
|
||||||
str = xstrdup(format);
|
str = xstrdup(format);
|
||||||
|
|
||||||
if (strstr(format, ARTIST_PLACEHOLDER) != NULL) {
|
if (strstr(format, PLACEHOLDER_ARTIST) != NULL) {
|
||||||
tmp = replaceString(str, ARTIST_PLACEHOLDER,
|
tmp = replaceString(str, PLACEHOLDER_ARTIST,
|
||||||
metadata_get_artist(mdata));
|
metadata_get_artist(mdata));
|
||||||
xfree(str);
|
xfree(str);
|
||||||
str = tmp;
|
str = tmp;
|
||||||
}
|
}
|
||||||
if (strstr(format, TITLE_PLACEHOLDER) != NULL) {
|
if (strstr(format, PLACEHOLDER_TITLE) != NULL) {
|
||||||
tmp = replaceString(str, TITLE_PLACEHOLDER,
|
tmp = replaceString(str, PLACEHOLDER_TITLE,
|
||||||
metadata_get_title(mdata));
|
metadata_get_title(mdata));
|
||||||
xfree(str);
|
xfree(str);
|
||||||
str = tmp;
|
str = tmp;
|
||||||
}
|
}
|
||||||
if (strstr(format, STRING_PLACEHOLDER) != NULL) {
|
if (strstr(format, PLACEHOLDER_STRING) != NULL) {
|
||||||
tmp = replaceString(str, STRING_PLACEHOLDER,
|
tmp = replaceString(str, PLACEHOLDER_STRING,
|
||||||
metadata_get_string(mdata));
|
metadata_get_string(mdata));
|
||||||
xfree(str);
|
xfree(str);
|
||||||
str = tmp;
|
str = tmp;
|
||||||
}
|
}
|
||||||
if (strstr(format, TRACK_PLACEHOLDER) != NULL) {
|
if (strstr(format, PLACEHOLDER_TRACK) != NULL) {
|
||||||
tmp = replaceString(str, TRACK_PLACEHOLDER,
|
tmp = replaceString(str, PLACEHOLDER_TRACK,
|
||||||
metadata_get_filename(mdata));
|
metadata_get_filename(mdata));
|
||||||
xfree(str);
|
xfree(str);
|
||||||
str = tmp;
|
str = tmp;
|
||||||
@ -418,8 +406,9 @@ getMetadata(const char *fileName)
|
|||||||
{
|
{
|
||||||
metadata_t *mdata;
|
metadata_t *mdata;
|
||||||
|
|
||||||
if (metadataFromProgram) {
|
if (cfg_get_metadata_program()) {
|
||||||
if ((mdata = metadata_program(fileName, cfg_normalize_strings())) == NULL)
|
if (NULL == (mdata = metadata_program(fileName,
|
||||||
|
cfg_get_metadata_normalize_strings())))
|
||||||
return (NULL);
|
return (NULL);
|
||||||
|
|
||||||
if (!metadata_program_update(mdata, METADATA_ALL)) {
|
if (!metadata_program_update(mdata, METADATA_ALL)) {
|
||||||
@ -427,7 +416,8 @@ getMetadata(const char *fileName)
|
|||||||
return (NULL);
|
return (NULL);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ((mdata = metadata_file(fileName, cfg_normalize_strings())) == NULL)
|
if (NULL == (mdata = metadata_file(fileName,
|
||||||
|
cfg_get_metadata_normalize_strings())))
|
||||||
return (NULL);
|
return (NULL);
|
||||||
|
|
||||||
if (!metadata_file_update(mdata)) {
|
if (!metadata_file_update(mdata)) {
|
||||||
@ -447,7 +437,7 @@ setMetadata(shout_t *shout, metadata_t *mdata, char **mdata_copy)
|
|||||||
const char *artist, *title;
|
const char *artist, *title;
|
||||||
int ret = SHOUTERR_SUCCESS;
|
int ret = SHOUTERR_SUCCESS;
|
||||||
|
|
||||||
if (cfg_no_metadata_updates())
|
if (cfg_get_metadata_no_updates())
|
||||||
return (SHOUTERR_SUCCESS);
|
return (SHOUTERR_SUCCESS);
|
||||||
|
|
||||||
if (mdata == NULL)
|
if (mdata == NULL)
|
||||||
@ -473,7 +463,8 @@ setMetadata(shout_t *shout, metadata_t *mdata, char **mdata_copy)
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((songInfo = getMetadataString(pezConfig->metadataFormat, mdata)) == NULL) {
|
songInfo = getMetadataString(cfg_get_metadata_format_str(), mdata);
|
||||||
|
if (songInfo == NULL) {
|
||||||
if (artist[0] == '\0' && title[0] == '\0')
|
if (artist[0] == '\0' && title[0] == '\0')
|
||||||
songInfo = xstrdup(metadata_get_string(mdata));
|
songInfo = xstrdup(metadata_get_string(mdata));
|
||||||
else
|
else
|
||||||
@ -531,8 +522,8 @@ openResource(shout_t *shout, const char *fileName, int *popenFlag,
|
|||||||
*songLen = 0;
|
*songLen = 0;
|
||||||
|
|
||||||
if (strcmp(fileName, "stdin") == 0) {
|
if (strcmp(fileName, "stdin") == 0) {
|
||||||
if (metadataFromProgram) {
|
if (cfg_get_metadata_program()) {
|
||||||
if ((mdata = getMetadata(pezConfig->metadataProgram)) == NULL)
|
if ((mdata = getMetadata(cfg_get_metadata_program())) == NULL)
|
||||||
return (NULL);
|
return (NULL);
|
||||||
if (setMetadata(shout, mdata, NULL) != SHOUTERR_SUCCESS) {
|
if (setMetadata(shout, mdata, NULL) != SHOUTERR_SUCCESS) {
|
||||||
metadata_free(&mdata);
|
metadata_free(&mdata);
|
||||||
@ -565,8 +556,8 @@ openResource(shout_t *shout, const char *fileName, int *popenFlag,
|
|||||||
return (filep);
|
return (filep);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (metadataFromProgram) {
|
if (cfg_get_metadata_program()) {
|
||||||
if ((mdata = getMetadata(pezConfig->metadataProgram)) == NULL)
|
if ((mdata = getMetadata(cfg_get_metadata_program())) == NULL)
|
||||||
return (NULL);
|
return (NULL);
|
||||||
} else {
|
} else {
|
||||||
if ((mdata = getMetadata(fileName)) == NULL)
|
if ((mdata = getMetadata(fileName)) == NULL)
|
||||||
@ -576,17 +567,18 @@ openResource(shout_t *shout, const char *fileName, int *popenFlag,
|
|||||||
*songLen = metadata_get_length(mdata);
|
*songLen = metadata_get_length(mdata);
|
||||||
|
|
||||||
*popenFlag = 0;
|
*popenFlag = 0;
|
||||||
if (pezConfig->reencode) {
|
if (cfg_get_stream_encoder()) {
|
||||||
int stderr_fd = -1;
|
int stderr_fd = -1;
|
||||||
|
|
||||||
pCommandString = buildCommandString(extension, fileName, mdata);
|
pCommandString = buildReencodeCommand(extension, fileName,
|
||||||
|
mdata);
|
||||||
if (mdata_p != NULL)
|
if (mdata_p != NULL)
|
||||||
*mdata_p = mdata;
|
*mdata_p = mdata;
|
||||||
else
|
else
|
||||||
metadata_free(&mdata);
|
metadata_free(&mdata);
|
||||||
log_info("running command: %s", pCommandString);
|
log_info("running command: %s", pCommandString);
|
||||||
|
|
||||||
if (cfg_quiet_stderr()) {
|
if (cfg_get_program_quiet_stderr()) {
|
||||||
int fd;
|
int fd;
|
||||||
|
|
||||||
stderr_fd = dup(fileno(stderr));
|
stderr_fd = dup(fileno(stderr));
|
||||||
@ -616,7 +608,7 @@ openResource(shout_t *shout, const char *fileName, int *popenFlag,
|
|||||||
}
|
}
|
||||||
xfree(pCommandString);
|
xfree(pCommandString);
|
||||||
|
|
||||||
if (cfg_quiet_stderr())
|
if (cfg_get_program_quiet_stderr())
|
||||||
dup2(stderr_fd, fileno(stderr));
|
dup2(stderr_fd, fileno(stderr));
|
||||||
|
|
||||||
if (stderr_fd > 2)
|
if (stderr_fd > 2)
|
||||||
@ -644,16 +636,17 @@ reconnectServer(shout_t *shout, int closeConn)
|
|||||||
unsigned int i;
|
unsigned int i;
|
||||||
int close_conn = closeConn;
|
int close_conn = closeConn;
|
||||||
|
|
||||||
log_warning("%s: connection lost", pezConfig->URL);
|
log_warning("%s: connection lost", cfg_get_server_hostname());
|
||||||
|
|
||||||
i = 0;
|
i = 0;
|
||||||
while (++i) {
|
while (++i) {
|
||||||
if (pezConfig->reconnectAttempts > 0)
|
if (cfg_get_server_reconnect_attempts() > 0)
|
||||||
log_notice("reconnect: %s: attempt #%u/%u ...",
|
log_notice("reconnect: %s: attempt #%u/%u ...",
|
||||||
pezConfig->URL, i, pezConfig->reconnectAttempts);
|
cfg_get_server_hostname(), i,
|
||||||
|
cfg_get_server_reconnect_attempts());
|
||||||
else
|
else
|
||||||
log_notice("reconnect: %s: attempt #%u ...",
|
log_notice("reconnect: %s: attempt #%u ...",
|
||||||
pezConfig->URL, i);
|
cfg_get_server_hostname(), i);
|
||||||
|
|
||||||
if (close_conn == 0)
|
if (close_conn == 0)
|
||||||
close_conn = 1;
|
close_conn = 1;
|
||||||
@ -661,15 +654,15 @@ reconnectServer(shout_t *shout, int closeConn)
|
|||||||
shout_close(shout);
|
shout_close(shout);
|
||||||
if (shout_open(shout) == SHOUTERR_SUCCESS) {
|
if (shout_open(shout) == SHOUTERR_SUCCESS) {
|
||||||
log_notice("reconnect: %s: success",
|
log_notice("reconnect: %s: success",
|
||||||
pezConfig->URL);
|
cfg_get_server_hostname());
|
||||||
return (1);
|
return (1);
|
||||||
}
|
}
|
||||||
|
|
||||||
log_warning("reconnect failed: %s: %s",
|
log_warning("reconnect failed: %s: %s",
|
||||||
pezConfig->URL, shout_get_error(shout));
|
cfg_get_server_hostname(), shout_get_error(shout));
|
||||||
|
|
||||||
if (pezConfig->reconnectAttempts > 0 &&
|
if (cfg_get_server_reconnect_attempts() > 0 &&
|
||||||
i >= pezConfig->reconnectAttempts)
|
i >= cfg_get_server_reconnect_attempts())
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (quit)
|
if (quit)
|
||||||
@ -742,7 +735,7 @@ sendStream(shout_t *shout, FILE *filepstream, const char *fileName,
|
|||||||
break;
|
break;
|
||||||
if (rereadPlaylist_notify) {
|
if (rereadPlaylist_notify) {
|
||||||
rereadPlaylist_notify = 0;
|
rereadPlaylist_notify = 0;
|
||||||
if (!pezConfig->fileNameIsProgram)
|
if (CFG_MEDIA_PLAYLIST == cfg_get_media_type())
|
||||||
log_notice("HUP signal received: playlist re-read scheduled");
|
log_notice("HUP signal received: playlist re-read scheduled");
|
||||||
}
|
}
|
||||||
if (skipTrack) {
|
if (skipTrack) {
|
||||||
@ -754,25 +747,24 @@ sendStream(shout_t *shout, FILE *filepstream, const char *fileName,
|
|||||||
ez_gettimeofday((void *)¤tTime);
|
ez_gettimeofday((void *)¤tTime);
|
||||||
|
|
||||||
if (queryMetadata ||
|
if (queryMetadata ||
|
||||||
(pezConfig->metadataRefreshInterval != -1
|
(cfg_get_metadata_refresh_interval() &&
|
||||||
&& (currentTime.tv_sec - callTime.tv_sec
|
(currentTime.tv_sec - callTime.tv_sec >=
|
||||||
>= pezConfig->metadataRefreshInterval)
|
cfg_get_metadata_refresh_interval()))) {
|
||||||
)
|
|
||||||
) {
|
|
||||||
queryMetadata = 0;
|
queryMetadata = 0;
|
||||||
if (metadataFromProgram) {
|
if (cfg_get_metadata_program()) {
|
||||||
ret = STREAM_UPDMDATA;
|
ret = STREAM_UPDMDATA;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
total += bytes_read;
|
total += bytes_read;
|
||||||
if (cfg_quiet_stderr() && cfg_verbosity()) {
|
if (cfg_get_program_quiet_stderr() &&
|
||||||
|
cfg_get_program_verbosity()) {
|
||||||
double oldTime, newTime;
|
double oldTime, newTime;
|
||||||
|
|
||||||
if (!isStdin && playlistMode) {
|
if (!isStdin && playlistMode) {
|
||||||
if (pezConfig->fileNameIsProgram) {
|
if (CFG_MEDIA_PROGRAM == cfg_get_media_type()) {
|
||||||
char *tmp = xstrdup(pezConfig->fileName);
|
char *tmp = xstrdup(cfg_get_media_filename());
|
||||||
printf(" [%s]", basename(tmp));
|
printf(" [%s]", basename(tmp));
|
||||||
xfree(tmp);
|
xfree(tmp);
|
||||||
} else
|
} else
|
||||||
@ -858,7 +850,7 @@ streamFile(shout_t *shout, const char *fileName)
|
|||||||
xfree(metaData);
|
xfree(metaData);
|
||||||
|
|
||||||
/* MP3 streams are special, so set the metadata explicitly: */
|
/* MP3 streams are special, so set the metadata explicitly: */
|
||||||
if (strcmp(pezConfig->format, MP3_FORMAT) == 0)
|
if (CFG_STREAM_MP3 == cfg_get_stream_format())
|
||||||
setMetadata(shout, mdata, NULL);
|
setMetadata(shout, mdata, NULL);
|
||||||
|
|
||||||
metadata_free(&mdata);
|
metadata_free(&mdata);
|
||||||
@ -892,15 +884,15 @@ streamFile(shout_t *shout, const char *fileName)
|
|||||||
}
|
}
|
||||||
if (ret == STREAM_UPDMDATA || queryMetadata) {
|
if (ret == STREAM_UPDMDATA || queryMetadata) {
|
||||||
queryMetadata = 0;
|
queryMetadata = 0;
|
||||||
if (cfg_no_metadata_updates())
|
if (cfg_get_metadata_no_updates())
|
||||||
continue;
|
continue;
|
||||||
if (metadataFromProgram) {
|
if (cfg_get_metadata_program()) {
|
||||||
char *mdataStr = NULL;
|
char *mdataStr = NULL;
|
||||||
metadata_t *prog_mdata;
|
metadata_t *prog_mdata;
|
||||||
|
|
||||||
log_info("running metadata program: %s",
|
log_info("running metadata program: %s",
|
||||||
pezConfig->metadataProgram);
|
cfg_get_metadata_program());
|
||||||
if ((prog_mdata = getMetadata(pezConfig->metadataProgram)) == NULL) {
|
if ((prog_mdata = getMetadata(cfg_get_metadata_program())) == NULL) {
|
||||||
retval = 0;
|
retval = 0;
|
||||||
ret = STREAM_DONE;
|
ret = STREAM_DONE;
|
||||||
continue;
|
continue;
|
||||||
@ -935,20 +927,21 @@ streamFile(shout_t *shout, const char *fileName)
|
|||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
streamPlaylist(shout_t *shout, const char *fileName)
|
streamPlaylist(shout_t *shout)
|
||||||
{
|
{
|
||||||
const char *song;
|
const char *song;
|
||||||
char lastSong[PATH_MAX];
|
char lastSong[PATH_MAX];
|
||||||
|
|
||||||
if (playlist == NULL) {
|
if (playlist == NULL) {
|
||||||
if (pezConfig->fileNameIsProgram) {
|
if (CFG_MEDIA_PROGRAM == cfg_get_media_type()) {
|
||||||
if ((playlist = playlist_program(fileName)) == NULL)
|
if ((playlist = playlist_program(cfg_get_media_filename())) == NULL)
|
||||||
return (0);
|
return (0);
|
||||||
} else {
|
} else {
|
||||||
if ((playlist = playlist_read(fileName)) == NULL)
|
if ((playlist = playlist_read(cfg_get_media_filename())) == NULL)
|
||||||
return (0);
|
return (0);
|
||||||
if (playlist_get_num_items(playlist) == 0)
|
if (playlist_get_num_items(playlist) == 0)
|
||||||
log_notice("%s: playlist empty", fileName);
|
log_notice("%s: playlist empty",
|
||||||
|
cfg_get_media_filename());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
@ -959,7 +952,8 @@ streamPlaylist(shout_t *shout, const char *fileName)
|
|||||||
playlist_rewind(playlist);
|
playlist_rewind(playlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pezConfig->fileNameIsProgram && pezConfig->shuffle)
|
if (CFG_MEDIA_PROGRAM != cfg_get_media_type() &&
|
||||||
|
cfg_get_media_shuffle())
|
||||||
playlist_shuffle(playlist);
|
playlist_shuffle(playlist);
|
||||||
|
|
||||||
while ((song = playlist_get_next(playlist)) != NULL) {
|
while ((song = playlist_get_next(playlist)) != NULL) {
|
||||||
@ -970,12 +964,12 @@ streamPlaylist(shout_t *shout, const char *fileName)
|
|||||||
break;
|
break;
|
||||||
if (rereadPlaylist) {
|
if (rereadPlaylist) {
|
||||||
rereadPlaylist = rereadPlaylist_notify = 0;
|
rereadPlaylist = rereadPlaylist_notify = 0;
|
||||||
if (pezConfig->fileNameIsProgram)
|
if (CFG_MEDIA_PROGRAM == cfg_get_media_type())
|
||||||
continue;
|
continue;
|
||||||
log_notice("rereading playlist");
|
log_notice("rereading playlist");
|
||||||
if (!playlist_reread(&playlist))
|
if (!playlist_reread(&playlist))
|
||||||
return (0);
|
return (0);
|
||||||
if (pezConfig->shuffle)
|
if (cfg_get_media_shuffle())
|
||||||
playlist_shuffle(playlist);
|
playlist_shuffle(playlist);
|
||||||
else {
|
else {
|
||||||
playlist_goto_entry(playlist, lastSong);
|
playlist_goto_entry(playlist, lastSong);
|
||||||
@ -992,9 +986,12 @@ int
|
|||||||
ez_shutdown(int exitval)
|
ez_shutdown(int exitval)
|
||||||
{
|
{
|
||||||
shout_shutdown();
|
shout_shutdown();
|
||||||
playlist_shutdown();
|
|
||||||
freeConfig(pezConfig);
|
playlist_exit();
|
||||||
|
cfg_encoder_exit();
|
||||||
|
cfg_decoder_exit();
|
||||||
log_exit();
|
log_exit();
|
||||||
|
cfg_exit();
|
||||||
|
|
||||||
return (exitval);
|
return (exitval);
|
||||||
}
|
}
|
||||||
@ -1003,12 +1000,6 @@ int
|
|||||||
main(int argc, char *argv[])
|
main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
const char *configFile;
|
|
||||||
const char *playlistFile;
|
|
||||||
char *host = NULL;
|
|
||||||
unsigned short port = 0;
|
|
||||||
char *mount = NULL;
|
|
||||||
shout_t *shout;
|
shout_t *shout;
|
||||||
extern char *optarg;
|
extern char *optarg;
|
||||||
extern int optind;
|
extern int optind;
|
||||||
@ -1017,38 +1008,15 @@ main(int argc, char *argv[])
|
|||||||
unsigned int i;
|
unsigned int i;
|
||||||
#endif
|
#endif
|
||||||
ret = 1;
|
ret = 1;
|
||||||
if (0 > cfg_cmdline_parse(argc, argv, &ret))
|
if (0 > cmdline_parse(argc, argv, &ret) ||
|
||||||
|
0 > log_init() ||
|
||||||
|
0 > cfg_decoder_init() ||
|
||||||
|
0 > cfg_encoder_init() ||
|
||||||
|
0 > playlist_init() ||
|
||||||
|
0 > cfg_reload())
|
||||||
return (ret);
|
return (ret);
|
||||||
log_init();
|
|
||||||
|
|
||||||
playlist_init();
|
|
||||||
shout_init();
|
shout_init();
|
||||||
|
|
||||||
pezConfig = getEZConfig();
|
|
||||||
|
|
||||||
playlistFile = cfg_shuffle_file();
|
|
||||||
if (playlistFile) {
|
|
||||||
playlist_t *pl;
|
|
||||||
const char *entry;
|
|
||||||
|
|
||||||
if (0 == strcmp(playlistFile, "-"))
|
|
||||||
pl = playlist_read(NULL);
|
|
||||||
else
|
|
||||||
pl = playlist_read(playlistFile);
|
|
||||||
|
|
||||||
if (pl == NULL)
|
|
||||||
return (ez_shutdown(1));
|
|
||||||
|
|
||||||
playlist_shuffle(pl);
|
|
||||||
while ((entry = playlist_get_next(pl)) != NULL)
|
|
||||||
printf("%s\n", entry);
|
|
||||||
|
|
||||||
playlist_free(&pl);
|
|
||||||
|
|
||||||
return (ez_shutdown(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
configFile = cfg_config_file();
|
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Attempt to open configFile here for a more meaningful error
|
* Attempt to open configFile here for a more meaningful error
|
||||||
@ -1058,23 +1026,25 @@ main(int argc, char *argv[])
|
|||||||
#ifdef HAVE_STAT
|
#ifdef HAVE_STAT
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
|
||||||
if (stat(configFile, &st) == -1) {
|
if (stat(cfg_get_program_config_file(), &st) == -1) {
|
||||||
log_error("%s: %s", configFile, strerror(errno));
|
log_error("%s: %s", cfg_get_program_config_file(),
|
||||||
|
strerror(errno));
|
||||||
return (ez_shutdown(2));
|
return (ez_shutdown(2));
|
||||||
}
|
}
|
||||||
if (cfg_verbosity() && (st.st_mode & (S_IRGRP | S_IROTH)))
|
if (cfg_get_program_verbosity() && (st.st_mode & (S_IRGRP | S_IROTH)))
|
||||||
log_warning("%s: group and/or world readable",
|
log_warning("%s: group and/or world readable",
|
||||||
configFile);
|
cfg_get_program_config_file());
|
||||||
if (st.st_mode & (S_IWGRP | S_IWOTH)) {
|
if (st.st_mode & (S_IWGRP | S_IWOTH)) {
|
||||||
log_error("%s: group and/or world writeable",
|
log_error("%s: group and/or world writeable",
|
||||||
configFile);
|
cfg_get_program_config_file());
|
||||||
return (ez_shutdown(2));
|
return (ez_shutdown(2));
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
FILE *tmp;
|
FILE *tmp;
|
||||||
|
|
||||||
if ((tmp = fopen(configFile, "r")) == NULL) {
|
if ((tmp = fopen(cfg_get_program_config_file(), "r")) == NULL) {
|
||||||
log_error("%s: %s", configFile, strerror(errno));
|
log_error("%s: %s", cfg_get_program_config_file(),
|
||||||
|
strerror(errno));
|
||||||
usage();
|
usage();
|
||||||
return (ez_shutdown(2));
|
return (ez_shutdown(2));
|
||||||
}
|
}
|
||||||
@ -1082,39 +1052,35 @@ main(int argc, char *argv[])
|
|||||||
#endif /* HAVE_STAT */
|
#endif /* HAVE_STAT */
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parseConfig(configFile))
|
if (0 > cfg_reload())
|
||||||
return (ez_shutdown(2));
|
return (ez_shutdown(2));
|
||||||
|
|
||||||
if (pezConfig->URL == NULL) {
|
if (!cfg_get_server_hostname() ||
|
||||||
log_error("%s: missing <url>", configFile);
|
!cfg_get_server_port()){
|
||||||
|
log_error("%s: missing server configuration",
|
||||||
|
cfg_get_program_config_file());
|
||||||
return (ez_shutdown(2));
|
return (ez_shutdown(2));
|
||||||
}
|
}
|
||||||
if (!urlParse(pezConfig->URL, &host, &port, &mount)) {
|
if (!cfg_get_server_password()) {
|
||||||
log_error("%s: <url>: must be of the form ``http://server:port/mountpoint''",
|
log_error("%s: <sourcepassword> missing",
|
||||||
configFile);
|
cfg_get_program_config_file());
|
||||||
return (ez_shutdown(2));
|
return (ez_shutdown(2));
|
||||||
}
|
}
|
||||||
if (pezConfig->password == NULL) {
|
if (!cfg_get_media_filename()) {
|
||||||
log_error("%s: <sourcepassword> missing", configFile);
|
log_error("%s: <filename> missing",
|
||||||
|
cfg_get_program_config_file());
|
||||||
return (ez_shutdown(2));
|
return (ez_shutdown(2));
|
||||||
}
|
}
|
||||||
if (pezConfig->fileName == NULL) {
|
if (CFG_STREAM_INVALID == cfg_get_stream_format()) {
|
||||||
log_error("%s: <filename> missing", configFile);
|
|
||||||
return (ez_shutdown(2));
|
|
||||||
}
|
|
||||||
if (pezConfig->format == NULL) {
|
|
||||||
log_error("%s: <format> missing or unsupported value",
|
log_error("%s: <format> missing or unsupported value",
|
||||||
configFile);
|
cfg_get_program_config_file());
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((shout = stream_setup(host, port, mount)) == NULL)
|
shout = stream_setup(cfg_get_server_hostname(),
|
||||||
|
cfg_get_server_port(), cfg_get_stream_mountpoint());
|
||||||
|
if (shout == NULL)
|
||||||
return (ez_shutdown(1));
|
return (ez_shutdown(1));
|
||||||
|
|
||||||
if (pezConfig->metadataProgram != NULL)
|
|
||||||
metadataFromProgram = 1;
|
|
||||||
else
|
|
||||||
metadataFromProgram = 0;
|
|
||||||
|
|
||||||
#ifdef HAVE_SIGNALS
|
#ifdef HAVE_SIGNALS
|
||||||
memset(&act, 0, sizeof(act));
|
memset(&act, 0, sizeof(act));
|
||||||
act.sa_handler = sig_handler;
|
act.sa_handler = sig_handler;
|
||||||
@ -1141,42 +1107,48 @@ main(int argc, char *argv[])
|
|||||||
if (shout_open(shout) == SHOUTERR_SUCCESS) {
|
if (shout_open(shout) == SHOUTERR_SUCCESS) {
|
||||||
int cont;
|
int cont;
|
||||||
|
|
||||||
log_notice("connected: http://%s:%hu%s", host, port, mount);
|
log_notice("connected: %s://%s:%u%s",
|
||||||
|
cfg_get_server_protocol_str(), cfg_get_server_hostname(),
|
||||||
|
cfg_get_server_port(), cfg_get_stream_mountpoint());
|
||||||
|
|
||||||
if (pezConfig->fileNameIsProgram ||
|
if (CFG_MEDIA_PROGRAM == cfg_get_media_type() ||
|
||||||
strrcasecmp(pezConfig->fileName, ".m3u") == 0 ||
|
CFG_MEDIA_PLAYLIST == cfg_get_media_type() ||
|
||||||
strrcasecmp(pezConfig->fileName, ".txt") == 0)
|
(CFG_MEDIA_AUTODETECT == cfg_get_media_type() &&
|
||||||
|
(strrcasecmp(cfg_get_media_filename(), ".m3u") == 0 ||
|
||||||
|
strrcasecmp(cfg_get_media_filename(), ".txt") == 0)))
|
||||||
playlistMode = 1;
|
playlistMode = 1;
|
||||||
else
|
else
|
||||||
playlistMode = 0;
|
playlistMode = 0;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if (playlistMode) {
|
if (playlistMode) {
|
||||||
cont = streamPlaylist(shout, pezConfig->fileName);
|
cont = streamPlaylist(shout);
|
||||||
} else {
|
} else {
|
||||||
cont = streamFile(shout, pezConfig->fileName);
|
cont = streamFile(shout,
|
||||||
|
cfg_get_media_filename());
|
||||||
}
|
}
|
||||||
if (quit)
|
if (quit)
|
||||||
break;
|
break;
|
||||||
if (pezConfig->streamOnce)
|
if (cfg_get_media_stream_once())
|
||||||
break;
|
break;
|
||||||
} while (cont);
|
} while (cont);
|
||||||
|
|
||||||
shout_close(shout);
|
shout_close(shout);
|
||||||
} else
|
} else
|
||||||
log_error("connection failed: http://%s:%hu%s: %s",
|
log_error("connection failed: %s://%s:%u%s: %s",
|
||||||
host, port, mount, shout_get_error(shout));
|
cfg_get_server_protocol_str(), cfg_get_server_hostname(),
|
||||||
|
cfg_get_server_port(), cfg_get_stream_mountpoint(),
|
||||||
|
shout_get_error(shout));
|
||||||
|
|
||||||
if (quit) {
|
if (quit) {
|
||||||
if (cfg_quiet_stderr() && cfg_verbosity())
|
if (cfg_get_program_quiet_stderr() &&
|
||||||
|
cfg_get_program_verbosity())
|
||||||
printf("\r");
|
printf("\r");
|
||||||
log_notice("INT or TERM signal received");
|
log_notice("INT or TERM signal received");
|
||||||
}
|
}
|
||||||
|
|
||||||
log_info("exiting");
|
log_info("exiting");
|
||||||
|
|
||||||
xfree(host);
|
|
||||||
xfree(mount);
|
|
||||||
playlist_free(&playlist);
|
playlist_free(&playlist);
|
||||||
|
|
||||||
return (ez_shutdown(0));
|
return (ez_shutdown(0));
|
||||||
|
12
src/log.c
12
src/log.c
@ -62,18 +62,18 @@ _vlog(enum log_levels lvl, const char *fmt, va_list ap)
|
|||||||
p = LOG_WARNING;
|
p = LOG_WARNING;
|
||||||
break;
|
break;
|
||||||
case NOTICE:
|
case NOTICE:
|
||||||
if (cfg_verbosity() < 1)
|
if (cfg_get_program_verbosity() < 1)
|
||||||
return;
|
return;
|
||||||
p = LOG_NOTICE;
|
p = LOG_NOTICE;
|
||||||
break;
|
break;
|
||||||
case INFO:
|
case INFO:
|
||||||
if (cfg_verbosity() < 2)
|
if (cfg_get_program_verbosity() < 2)
|
||||||
return;
|
return;
|
||||||
p = LOG_INFO;
|
p = LOG_INFO;
|
||||||
break;
|
break;
|
||||||
case DEBUG:
|
case DEBUG:
|
||||||
default:
|
default:
|
||||||
if (cfg_verbosity() < 3)
|
if (cfg_get_program_verbosity() < 3)
|
||||||
return;
|
return;
|
||||||
p = LOG_DEBUG;
|
p = LOG_DEBUG;
|
||||||
break;
|
break;
|
||||||
@ -84,11 +84,13 @@ _vlog(enum log_levels lvl, const char *fmt, va_list ap)
|
|||||||
va_end(ap2);
|
va_end(ap2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
int
|
||||||
log_init(void)
|
log_init(void)
|
||||||
{
|
{
|
||||||
openlog(cfg_progname(), LOG_PID|LOG_CONS|LOG_NDELAY|LOG_PERROR,
|
openlog(cfg_get_program_name(),
|
||||||
|
LOG_PID|LOG_CONS|LOG_NDELAY|LOG_PERROR,
|
||||||
LOG_USER);
|
LOG_USER);
|
||||||
|
return (0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -28,7 +28,7 @@ enum log_levels {
|
|||||||
DEBUG
|
DEBUG
|
||||||
};
|
};
|
||||||
|
|
||||||
void log_init(void);
|
int log_init(void);
|
||||||
void log_exit(void);
|
void log_exit(void);
|
||||||
|
|
||||||
void log_syserr(enum log_levels, int, const char *);
|
void log_syserr(enum log_levels, int, const char *);
|
||||||
|
@ -156,7 +156,7 @@ playlist_run_program(playlist_t *pl)
|
|||||||
return ((const char *)pl->prog_track);
|
return ((const char *)pl->prog_track);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
int
|
||||||
playlist_init(void)
|
playlist_init(void)
|
||||||
{
|
{
|
||||||
#ifdef HAVE_RANDOM
|
#ifdef HAVE_RANDOM
|
||||||
@ -168,9 +168,10 @@ playlist_init(void)
|
|||||||
#else
|
#else
|
||||||
srand((unsigned int)time(NULL));
|
srand((unsigned int)time(NULL));
|
||||||
#endif /* HAVE_RANDOM */
|
#endif /* HAVE_RANDOM */
|
||||||
|
return (0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void playlist_shutdown(void) {}
|
void playlist_exit(void) {}
|
||||||
|
|
||||||
playlist_t *
|
playlist_t *
|
||||||
playlist_read(const char *filename)
|
playlist_read(const char *filename)
|
||||||
|
@ -23,12 +23,12 @@ typedef struct playlist playlist_t;
|
|||||||
* Initialize the playlist routines. Should be called before any of the other
|
* Initialize the playlist routines. Should be called before any of the other
|
||||||
* playlist functions.
|
* playlist functions.
|
||||||
*/
|
*/
|
||||||
void playlist_init(void);
|
int playlist_init(void);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Clean up for clean shutdowns. No-op at the moment.
|
* Clean up for clean shutdowns. No-op at the moment.
|
||||||
*/
|
*/
|
||||||
void playlist_shutdown(void);
|
void playlist_exit(void);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Read a playlist file (in .M3U format), and return a new playlist handler
|
* Read a playlist file (in .M3U format), and return a new playlist handler
|
||||||
|
49
src/util.c
49
src/util.c
@ -36,7 +36,7 @@
|
|||||||
#endif
|
#endif
|
||||||
#include <shout/shout.h>
|
#include <shout/shout.h>
|
||||||
|
|
||||||
#include "configfile.h"
|
#include "cfg.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "xalloc.h"
|
#include "xalloc.h"
|
||||||
@ -45,8 +45,6 @@
|
|||||||
# define BUFSIZ 1024
|
# define BUFSIZ 1024
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
extern EZCONFIG *pezConfig;
|
|
||||||
|
|
||||||
char * iconvert(const char *, const char *, const char *, int);
|
char * iconvert(const char *, const char *, const char *, int);
|
||||||
|
|
||||||
int
|
int
|
||||||
@ -111,7 +109,7 @@ stream_setup(const char *host, unsigned short port, const char *mount)
|
|||||||
shout_free(shout);
|
shout_free(shout);
|
||||||
return (NULL);
|
return (NULL);
|
||||||
}
|
}
|
||||||
if (shout_set_password(shout, pezConfig->password) != SHOUTERR_SUCCESS) {
|
if (shout_set_password(shout, cfg_get_server_password()) != SHOUTERR_SUCCESS) {
|
||||||
log_error("shout_set_password: %s",
|
log_error("shout_set_password: %s",
|
||||||
shout_get_error(shout));
|
shout_get_error(shout));
|
||||||
shout_free(shout);
|
shout_free(shout);
|
||||||
@ -130,15 +128,15 @@ stream_setup(const char *host, unsigned short port, const char *mount)
|
|||||||
return (NULL);
|
return (NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!strcmp(pezConfig->format, MP3_FORMAT) &&
|
if (CFG_STREAM_MP3 == cfg_get_stream_format() &&
|
||||||
shout_set_format(shout, SHOUT_FORMAT_MP3) != SHOUTERR_SUCCESS) {
|
shout_set_format(shout, SHOUT_FORMAT_MP3) != SHOUTERR_SUCCESS) {
|
||||||
log_error("shout_set_format(MP3): %s",
|
log_error("shout_set_format(MP3): %s",
|
||||||
shout_get_error(shout));
|
shout_get_error(shout));
|
||||||
shout_free(shout);
|
shout_free(shout);
|
||||||
return (NULL);
|
return (NULL);
|
||||||
}
|
}
|
||||||
if ((!strcmp(pezConfig->format, VORBIS_FORMAT) ||
|
if ((CFG_STREAM_VORBIS == cfg_get_stream_format() ||
|
||||||
!strcmp(pezConfig->format, THEORA_FORMAT)) &&
|
CFG_STREAM_THEORA == cfg_get_stream_format()) &&
|
||||||
shout_set_format(shout, SHOUT_FORMAT_OGG) != SHOUTERR_SUCCESS) {
|
shout_set_format(shout, SHOUT_FORMAT_OGG) != SHOUTERR_SUCCESS) {
|
||||||
log_error("shout_set_format(OGG): %s",
|
log_error("shout_set_format(OGG): %s",
|
||||||
shout_get_error(shout));
|
shout_get_error(shout));
|
||||||
@ -146,71 +144,70 @@ stream_setup(const char *host, unsigned short port, const char *mount)
|
|||||||
return (NULL);
|
return (NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pezConfig->username &&
|
if (shout_set_user(shout, cfg_get_server_user()) != SHOUTERR_SUCCESS) {
|
||||||
shout_set_user(shout, pezConfig->username) != SHOUTERR_SUCCESS) {
|
|
||||||
log_error("shout_set_user: %s",
|
log_error("shout_set_user: %s",
|
||||||
shout_get_error(shout));
|
shout_get_error(shout));
|
||||||
shout_free(shout);
|
shout_free(shout);
|
||||||
return (NULL);
|
return (NULL);
|
||||||
}
|
}
|
||||||
if (pezConfig->serverName &&
|
if (cfg_get_stream_name() &&
|
||||||
shout_set_name(shout, pezConfig->serverName) != SHOUTERR_SUCCESS) {
|
shout_set_name(shout, cfg_get_stream_name()) != SHOUTERR_SUCCESS) {
|
||||||
log_error("shout_set_name: %s",
|
log_error("shout_set_name: %s",
|
||||||
shout_get_error(shout));
|
shout_get_error(shout));
|
||||||
shout_free(shout);
|
shout_free(shout);
|
||||||
return (NULL);
|
return (NULL);
|
||||||
}
|
}
|
||||||
if (pezConfig->serverURL &&
|
if (cfg_get_stream_url() &&
|
||||||
shout_set_url(shout, pezConfig->serverURL) != SHOUTERR_SUCCESS) {
|
shout_set_url(shout, cfg_get_stream_url()) != SHOUTERR_SUCCESS) {
|
||||||
log_error("shout_set_url: %s",
|
log_error("shout_set_url: %s",
|
||||||
shout_get_error(shout));
|
shout_get_error(shout));
|
||||||
shout_free(shout);
|
shout_free(shout);
|
||||||
return (NULL);
|
return (NULL);
|
||||||
}
|
}
|
||||||
if (pezConfig->serverGenre &&
|
if (cfg_get_stream_genre() &&
|
||||||
shout_set_genre(shout, pezConfig->serverGenre) != SHOUTERR_SUCCESS) {
|
shout_set_genre(shout, cfg_get_stream_genre()) != SHOUTERR_SUCCESS) {
|
||||||
log_error("shout_set_genre: %s",
|
log_error("shout_set_genre: %s",
|
||||||
shout_get_error(shout));
|
shout_get_error(shout));
|
||||||
shout_free(shout);
|
shout_free(shout);
|
||||||
return (NULL);
|
return (NULL);
|
||||||
}
|
}
|
||||||
if (pezConfig->serverDescription &&
|
if (cfg_get_stream_description() &&
|
||||||
shout_set_description(shout, pezConfig->serverDescription) != SHOUTERR_SUCCESS) {
|
shout_set_description(shout, cfg_get_stream_description()) != SHOUTERR_SUCCESS) {
|
||||||
log_error("shout_set_description: %s",
|
log_error("shout_set_description: %s",
|
||||||
shout_get_error(shout));
|
shout_get_error(shout));
|
||||||
shout_free(shout);
|
shout_free(shout);
|
||||||
return (NULL);
|
return (NULL);
|
||||||
}
|
}
|
||||||
if (pezConfig->serverBitrate &&
|
if (cfg_get_stream_bitrate() &&
|
||||||
shout_set_audio_info(shout, SHOUT_AI_BITRATE, pezConfig->serverBitrate) != SHOUTERR_SUCCESS) {
|
shout_set_audio_info(shout, SHOUT_AI_BITRATE, cfg_get_stream_bitrate()) != SHOUTERR_SUCCESS) {
|
||||||
log_error("shout_set_audio_info(AI_BITRATE): %s",
|
log_error("shout_set_audio_info(AI_BITRATE): %s",
|
||||||
shout_get_error(shout));
|
shout_get_error(shout));
|
||||||
shout_free(shout);
|
shout_free(shout);
|
||||||
return (NULL);
|
return (NULL);
|
||||||
}
|
}
|
||||||
if (pezConfig->serverChannels &&
|
if (cfg_get_stream_channels() &&
|
||||||
shout_set_audio_info(shout, SHOUT_AI_CHANNELS, pezConfig->serverChannels) != SHOUTERR_SUCCESS) {
|
shout_set_audio_info(shout, SHOUT_AI_CHANNELS, cfg_get_stream_channels()) != SHOUTERR_SUCCESS) {
|
||||||
log_error("shout_set_audio_info(AI_CHANNELS): %s",
|
log_error("shout_set_audio_info(AI_CHANNELS): %s",
|
||||||
shout_get_error(shout));
|
shout_get_error(shout));
|
||||||
shout_free(shout);
|
shout_free(shout);
|
||||||
return (NULL);
|
return (NULL);
|
||||||
}
|
}
|
||||||
if (pezConfig->serverSamplerate &&
|
if (cfg_get_stream_samplerate() &&
|
||||||
shout_set_audio_info(shout, SHOUT_AI_SAMPLERATE, pezConfig->serverSamplerate) != SHOUTERR_SUCCESS) {
|
shout_set_audio_info(shout, SHOUT_AI_SAMPLERATE, cfg_get_stream_samplerate()) != SHOUTERR_SUCCESS) {
|
||||||
log_error("shout_set_audio_info(AI_SAMPLERATE): %s",
|
log_error("shout_set_audio_info(AI_SAMPLERATE): %s",
|
||||||
shout_get_error(shout));
|
shout_get_error(shout));
|
||||||
shout_free(shout);
|
shout_free(shout);
|
||||||
return (NULL);
|
return (NULL);
|
||||||
}
|
}
|
||||||
if (pezConfig->serverQuality &&
|
if (cfg_get_stream_quality() &&
|
||||||
shout_set_audio_info(shout, SHOUT_AI_QUALITY, pezConfig->serverQuality) != SHOUTERR_SUCCESS) {
|
shout_set_audio_info(shout, SHOUT_AI_QUALITY, cfg_get_stream_quality()) != SHOUTERR_SUCCESS) {
|
||||||
log_error("shout_set_audio_info(AI_QUALITY): %s",
|
log_error("shout_set_audio_info(AI_QUALITY): %s",
|
||||||
shout_get_error(shout));
|
shout_get_error(shout));
|
||||||
shout_free(shout);
|
shout_free(shout);
|
||||||
return (NULL);
|
return (NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shout_set_public(shout, (unsigned int)pezConfig->serverPublic) != SHOUTERR_SUCCESS) {
|
if (shout_set_public(shout, (unsigned int)cfg_get_stream_server_public()) != SHOUTERR_SUCCESS) {
|
||||||
log_error("shout_set_public: %s",
|
log_error("shout_set_public: %s",
|
||||||
shout_get_error(shout));
|
shout_get_error(shout));
|
||||||
shout_free(shout);
|
shout_free(shout);
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
#ifndef __XALLOC_H__
|
#ifndef __XALLOC_H__
|
||||||
#define __XALLOC_H__
|
#define __XALLOC_H__
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
#define xmalloc(s) xmalloc_c(s, __FILE__, __LINE__)
|
#define xmalloc(s) xmalloc_c(s, __FILE__, __LINE__)
|
||||||
#define xcalloc(n, s) xcalloc_c(n, s, __FILE__, __LINE__)
|
#define xcalloc(n, s) xcalloc_c(n, s, __FILE__, __LINE__)
|
||||||
#define xreallocarray(p, n, s) xreallocarray_c(p, n, s, __FILE__, __LINE__)
|
#define xreallocarray(p, n, s) xreallocarray_c(p, n, s, __FILE__, __LINE__)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user