1
0
mirror of https://gitlab.xiph.org/xiph/ezstream.git synced 2024-10-13 04:53:36 -04: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:
Moritz Grimm 2015-05-02 12:48:53 +02:00
parent ffa2a01c96
commit 43e48648fa
23 changed files with 2524 additions and 1274 deletions

3
NEWS
View File

@ -5,6 +5,9 @@ Changes in X.X.X, released on XXXX-XX-XX:
* The behaviour of the -s command line argument was changed:
To shuffle lines from standard input, the special file name "-" needs
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:

View 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>

View File

@ -6,7 +6,11 @@ bin_SCRIPTS = ezstream-file.sh
noinst_HEADERS = \
attributes.h \
cfg.h \
configfile.h \
cfg_decoder.h \
cfg_encoder.h \
cfg_private.h \
cfg_xmlfile.h \
cmdline.h \
ezstream.h \
log.h \
metadata.h \
@ -15,7 +19,10 @@ noinst_HEADERS = \
xalloc.h
ezstream_SOURCES = \
cfg.c \
configfile.c \
cfg_decoder.c \
cfg_encoder.c \
cfg_xmlfile.c \
cmdline.c \
ezstream.c \
log.c \
metadata.c \

975
src/cfg.c

File diff suppressed because it is too large Load Diff

171
src/cfg.h
View File

@ -17,19 +17,170 @@
#ifndef __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 *
cfg_progname(void);
cfg_get_program_name(void);
enum cfg_config_type
cfg_get_program_config_type(void);
const char *
cfg_config_file(void);
int cfg_no_metadata_updates(void);
int cfg_normalize_strings(void);
int cfg_quiet_stderr(void);
const char *
cfg_shuffle_file(void);
cfg_get_program_config_file(void);
int cfg_get_program_quiet_stderr(void);
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__ */

219
src/cfg_decoder.c Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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__ */

View File

@ -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);
}

View File

@ -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__ */

View File

@ -28,7 +28,7 @@
#include <shout/shout.h>
#include "cfg.h"
#include "configfile.h"
#include "cmdline.h"
#include "log.h"
#include "metadata.h"
#include "playlist.h"
@ -41,9 +41,6 @@
#define STREAM_SERVERR 3
#define STREAM_UPDMDATA 4
int metadataFromProgram;
EZCONFIG *pezConfig = NULL;
playlist_t *playlist = NULL;
int playlistMode = 0;
unsigned int resource_errors = 0;
@ -79,7 +76,7 @@ typedef struct tag_ID3Tag {
int urlParse(const char *, char **, unsigned short *, char **);
char * shellQuote(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 *);
metadata_t * getMetadata(const 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 *,
struct timeval *);
int streamFile(shout_t *, const char *);
int streamPlaylist(shout_t *, const char *);
int streamPlaylist(shout_t *);
int ez_shutdown(int);
#ifdef HAVE_SIGNALS
@ -239,44 +236,47 @@ replaceString(const char *source, const char *from, const char *to)
}
char *
buildCommandString(const char *extension, const char *fileName,
metadata_t *mdata)
buildReencodeCommand(const char *extension, const char *fileName,
metadata_t *mdata)
{
char *commandString = NULL;
size_t commandStringLen = 0;
char *encoder = NULL;
char *decoder = NULL;
char *newDecoder = NULL;
char *newEncoder = NULL;
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);
cfg_decoder_t decoder;
cfg_encoder_t encoder;
char *dec_str, *enc_str;
char *commandString;
size_t commandStringLen;
char *localTitle, *localArtist, *localMetaString;
decoder = xstrdup(getFormatDecoder(extension));
if (strlen(decoder) == 0) {
log_error("cannot decode: %s: unknown file extension %s",
decoder = cfg_decoder_find(extension);
if (!decoder) {
log_error("cannot decode: %s: unsupported file extension %s",
fileName, extension);
xfree(localTitle);
xfree(localArtist);
xfree(localMetaString);
xfree(decoder);
return (NULL);
}
newDecoder = replaceString(decoder, TRACK_PLACEHOLDER, fileName);
if (strstr(decoder, ARTIST_PLACEHOLDER) != NULL) {
char *tmpStr = replaceString(newDecoder, ARTIST_PLACEHOLDER,
localArtist);
xfree(newDecoder);
newDecoder = tmpStr;
encoder = cfg_encoder_get(cfg_get_stream_encoder());
if (!encoder) {
log_error("cannot encode: %s: unknown encoder",
cfg_get_stream_encoder());
return (NULL);
}
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);
xfree(newDecoder);
newDecoder = tmpStr;
xfree(dec_str);
dec_str = tmpStr;
}
/*
* if meta
@ -288,89 +288,77 @@ buildCommandString(const char *extension, const char *fileName,
* else
* replacemeta
*/
if (strstr(decoder, METADATA_PLACEHOLDER) != NULL) {
if (metadataFromProgram && pezConfig->metadataFormat != NULL) {
char *mdataString = getMetadataString(pezConfig->metadataFormat, mdata);
char *tmpStr = replaceString(newDecoder,
METADATA_PLACEHOLDER, mdataString);
xfree(newDecoder);
if (strstr(dec_str, PLACEHOLDER_METADATA) != NULL) {
if (cfg_get_metadata_program() &&
cfg_get_metadata_format_str()) {
char *mdataString = getMetadataString(cfg_get_metadata_format_str(),
mdata);
char *tmpStr = replaceString(dec_str,
PLACEHOLDER_METADATA, mdataString);
xfree(dec_str);
xfree(mdataString);
newDecoder = tmpStr;
dec_str = tmpStr;
} else {
if (!metadataFromProgram && strstr(decoder, TITLE_PLACEHOLDER) != NULL) {
char *tmpStr = replaceString(newDecoder,
METADATA_PLACEHOLDER, "");
xfree(newDecoder);
newDecoder = tmpStr;
if (!cfg_get_metadata_program() &&
strstr(dec_str, PLACEHOLDER_TITLE) != NULL) {
char *tmpStr = replaceString(dec_str,
PLACEHOLDER_METADATA, "");
xfree(dec_str);
dec_str = tmpStr;
} else {
char *tmpStr = replaceString(newDecoder,
METADATA_PLACEHOLDER, localMetaString);
xfree(newDecoder);
newDecoder = tmpStr;
char *tmpStr = replaceString(dec_str,
PLACEHOLDER_METADATA, localMetaString);
xfree(dec_str);
dec_str = tmpStr;
}
}
}
encoder = xstrdup(getFormatEncoder(pezConfig->format));
if (strlen(encoder) == 0) {
log_notice("passing through%s%s data from the decoder",
(strcmp(pezConfig->format, THEORA_FORMAT) != 0) ? " (unsupported) " : " ",
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,
enc_str = replaceString(cfg_encoder_get_program(encoder),
PLACEHOLDER_ARTIST, localArtist);
if (strstr(enc_str, PLACEHOLDER_TITLE) != NULL) {
char *tmpStr = replaceString(enc_str, PLACEHOLDER_TITLE,
localTitle);
xfree(newEncoder);
newEncoder = tmpStr;
xfree(enc_str);
enc_str = tmpStr;
}
if (strstr(encoder, METADATA_PLACEHOLDER) != NULL) {
if (metadataFromProgram && pezConfig->metadataFormat != NULL) {
char *mdataString = getMetadataString(pezConfig->metadataFormat, mdata);
char *tmpStr = replaceString(newEncoder,
METADATA_PLACEHOLDER, mdataString);
xfree(newEncoder);
if (strstr(enc_str, PLACEHOLDER_METADATA) != NULL) {
if (cfg_get_metadata_program() &&
cfg_get_metadata_format_str()) {
char *mdataString = getMetadataString(cfg_get_metadata_format_str(),
mdata);
char *tmpStr = replaceString(enc_str,
PLACEHOLDER_METADATA, mdataString);
xfree(enc_str);
xfree(mdataString);
newEncoder = tmpStr;
enc_str = tmpStr;
} else {
if (!metadataFromProgram && strstr(encoder, TITLE_PLACEHOLDER) != NULL) {
char *tmpStr = replaceString(newEncoder,
METADATA_PLACEHOLDER, "");
xfree(newEncoder);
newEncoder = tmpStr;
if (!cfg_get_metadata_program() &&
strstr(enc_str, PLACEHOLDER_TITLE) != NULL) {
char *tmpStr = replaceString(enc_str,
PLACEHOLDER_METADATA, "");
xfree(enc_str);
enc_str = tmpStr;
} else {
char *tmpStr = replaceString(newEncoder,
METADATA_PLACEHOLDER, localMetaString);
xfree(newEncoder);
newEncoder = tmpStr;
char *tmpStr = replaceString(enc_str,
PLACEHOLDER_METADATA, localMetaString);
xfree(enc_str);
enc_str = tmpStr;
}
}
}
commandStringLen = strlen(newDecoder) + strlen(" | ") +
strlen(newEncoder) + 1;
commandStringLen = strlen(dec_str) + strlen(" | ") +
strlen(enc_str) + 1;
commandString = xcalloc(commandStringLen, sizeof(char));
snprintf(commandString, commandStringLen, "%s | %s", newDecoder,
newEncoder);
snprintf(commandString, commandStringLen, "%s | %s", dec_str,
enc_str);
xfree(localTitle);
xfree(localArtist);
xfree(localMetaString);
xfree(decoder);
xfree(encoder);
xfree(newDecoder);
xfree(newEncoder);
xfree(dec_str);
xfree(enc_str);
return (commandString);
}
@ -385,26 +373,26 @@ getMetadataString(const char *format, metadata_t *mdata)
str = xstrdup(format);
if (strstr(format, ARTIST_PLACEHOLDER) != NULL) {
tmp = replaceString(str, ARTIST_PLACEHOLDER,
if (strstr(format, PLACEHOLDER_ARTIST) != NULL) {
tmp = replaceString(str, PLACEHOLDER_ARTIST,
metadata_get_artist(mdata));
xfree(str);
str = tmp;
}
if (strstr(format, TITLE_PLACEHOLDER) != NULL) {
tmp = replaceString(str, TITLE_PLACEHOLDER,
if (strstr(format, PLACEHOLDER_TITLE) != NULL) {
tmp = replaceString(str, PLACEHOLDER_TITLE,
metadata_get_title(mdata));
xfree(str);
str = tmp;
}
if (strstr(format, STRING_PLACEHOLDER) != NULL) {
tmp = replaceString(str, STRING_PLACEHOLDER,
if (strstr(format, PLACEHOLDER_STRING) != NULL) {
tmp = replaceString(str, PLACEHOLDER_STRING,
metadata_get_string(mdata));
xfree(str);
str = tmp;
}
if (strstr(format, TRACK_PLACEHOLDER) != NULL) {
tmp = replaceString(str, TRACK_PLACEHOLDER,
if (strstr(format, PLACEHOLDER_TRACK) != NULL) {
tmp = replaceString(str, PLACEHOLDER_TRACK,
metadata_get_filename(mdata));
xfree(str);
str = tmp;
@ -418,8 +406,9 @@ getMetadata(const char *fileName)
{
metadata_t *mdata;
if (metadataFromProgram) {
if ((mdata = metadata_program(fileName, cfg_normalize_strings())) == NULL)
if (cfg_get_metadata_program()) {
if (NULL == (mdata = metadata_program(fileName,
cfg_get_metadata_normalize_strings())))
return (NULL);
if (!metadata_program_update(mdata, METADATA_ALL)) {
@ -427,7 +416,8 @@ getMetadata(const char *fileName)
return (NULL);
}
} else {
if ((mdata = metadata_file(fileName, cfg_normalize_strings())) == NULL)
if (NULL == (mdata = metadata_file(fileName,
cfg_get_metadata_normalize_strings())))
return (NULL);
if (!metadata_file_update(mdata)) {
@ -447,7 +437,7 @@ setMetadata(shout_t *shout, metadata_t *mdata, char **mdata_copy)
const char *artist, *title;
int ret = SHOUTERR_SUCCESS;
if (cfg_no_metadata_updates())
if (cfg_get_metadata_no_updates())
return (SHOUTERR_SUCCESS);
if (mdata == NULL)
@ -473,7 +463,8 @@ setMetadata(shout_t *shout, metadata_t *mdata, char **mdata_copy)
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')
songInfo = xstrdup(metadata_get_string(mdata));
else
@ -531,8 +522,8 @@ openResource(shout_t *shout, const char *fileName, int *popenFlag,
*songLen = 0;
if (strcmp(fileName, "stdin") == 0) {
if (metadataFromProgram) {
if ((mdata = getMetadata(pezConfig->metadataProgram)) == NULL)
if (cfg_get_metadata_program()) {
if ((mdata = getMetadata(cfg_get_metadata_program())) == NULL)
return (NULL);
if (setMetadata(shout, mdata, NULL) != SHOUTERR_SUCCESS) {
metadata_free(&mdata);
@ -565,8 +556,8 @@ openResource(shout_t *shout, const char *fileName, int *popenFlag,
return (filep);
}
if (metadataFromProgram) {
if ((mdata = getMetadata(pezConfig->metadataProgram)) == NULL)
if (cfg_get_metadata_program()) {
if ((mdata = getMetadata(cfg_get_metadata_program())) == NULL)
return (NULL);
} else {
if ((mdata = getMetadata(fileName)) == NULL)
@ -576,17 +567,18 @@ openResource(shout_t *shout, const char *fileName, int *popenFlag,
*songLen = metadata_get_length(mdata);
*popenFlag = 0;
if (pezConfig->reencode) {
if (cfg_get_stream_encoder()) {
int stderr_fd = -1;
pCommandString = buildCommandString(extension, fileName, mdata);
pCommandString = buildReencodeCommand(extension, fileName,
mdata);
if (mdata_p != NULL)
*mdata_p = mdata;
else
metadata_free(&mdata);
log_info("running command: %s", pCommandString);
if (cfg_quiet_stderr()) {
if (cfg_get_program_quiet_stderr()) {
int fd;
stderr_fd = dup(fileno(stderr));
@ -616,7 +608,7 @@ openResource(shout_t *shout, const char *fileName, int *popenFlag,
}
xfree(pCommandString);
if (cfg_quiet_stderr())
if (cfg_get_program_quiet_stderr())
dup2(stderr_fd, fileno(stderr));
if (stderr_fd > 2)
@ -644,16 +636,17 @@ reconnectServer(shout_t *shout, int closeConn)
unsigned int i;
int close_conn = closeConn;
log_warning("%s: connection lost", pezConfig->URL);
log_warning("%s: connection lost", cfg_get_server_hostname());
i = 0;
while (++i) {
if (pezConfig->reconnectAttempts > 0)
if (cfg_get_server_reconnect_attempts() > 0)
log_notice("reconnect: %s: attempt #%u/%u ...",
pezConfig->URL, i, pezConfig->reconnectAttempts);
cfg_get_server_hostname(), i,
cfg_get_server_reconnect_attempts());
else
log_notice("reconnect: %s: attempt #%u ...",
pezConfig->URL, i);
cfg_get_server_hostname(), i);
if (close_conn == 0)
close_conn = 1;
@ -661,15 +654,15 @@ reconnectServer(shout_t *shout, int closeConn)
shout_close(shout);
if (shout_open(shout) == SHOUTERR_SUCCESS) {
log_notice("reconnect: %s: success",
pezConfig->URL);
cfg_get_server_hostname());
return (1);
}
log_warning("reconnect failed: %s: %s",
pezConfig->URL, shout_get_error(shout));
cfg_get_server_hostname(), shout_get_error(shout));
if (pezConfig->reconnectAttempts > 0 &&
i >= pezConfig->reconnectAttempts)
if (cfg_get_server_reconnect_attempts() > 0 &&
i >= cfg_get_server_reconnect_attempts())
break;
if (quit)
@ -742,7 +735,7 @@ sendStream(shout_t *shout, FILE *filepstream, const char *fileName,
break;
if (rereadPlaylist_notify) {
rereadPlaylist_notify = 0;
if (!pezConfig->fileNameIsProgram)
if (CFG_MEDIA_PLAYLIST == cfg_get_media_type())
log_notice("HUP signal received: playlist re-read scheduled");
}
if (skipTrack) {
@ -754,25 +747,24 @@ sendStream(shout_t *shout, FILE *filepstream, const char *fileName,
ez_gettimeofday((void *)&currentTime);
if (queryMetadata ||
(pezConfig->metadataRefreshInterval != -1
&& (currentTime.tv_sec - callTime.tv_sec
>= pezConfig->metadataRefreshInterval)
)
) {
(cfg_get_metadata_refresh_interval() &&
(currentTime.tv_sec - callTime.tv_sec >=
cfg_get_metadata_refresh_interval()))) {
queryMetadata = 0;
if (metadataFromProgram) {
if (cfg_get_metadata_program()) {
ret = STREAM_UPDMDATA;
break;
}
}
total += bytes_read;
if (cfg_quiet_stderr() && cfg_verbosity()) {
if (cfg_get_program_quiet_stderr() &&
cfg_get_program_verbosity()) {
double oldTime, newTime;
if (!isStdin && playlistMode) {
if (pezConfig->fileNameIsProgram) {
char *tmp = xstrdup(pezConfig->fileName);
if (CFG_MEDIA_PROGRAM == cfg_get_media_type()) {
char *tmp = xstrdup(cfg_get_media_filename());
printf(" [%s]", basename(tmp));
xfree(tmp);
} else
@ -858,7 +850,7 @@ streamFile(shout_t *shout, const char *fileName)
xfree(metaData);
/* 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);
metadata_free(&mdata);
@ -892,15 +884,15 @@ streamFile(shout_t *shout, const char *fileName)
}
if (ret == STREAM_UPDMDATA || queryMetadata) {
queryMetadata = 0;
if (cfg_no_metadata_updates())
if (cfg_get_metadata_no_updates())
continue;
if (metadataFromProgram) {
if (cfg_get_metadata_program()) {
char *mdataStr = NULL;
metadata_t *prog_mdata;
log_info("running metadata program: %s",
pezConfig->metadataProgram);
if ((prog_mdata = getMetadata(pezConfig->metadataProgram)) == NULL) {
cfg_get_metadata_program());
if ((prog_mdata = getMetadata(cfg_get_metadata_program())) == NULL) {
retval = 0;
ret = STREAM_DONE;
continue;
@ -935,20 +927,21 @@ streamFile(shout_t *shout, const char *fileName)
}
int
streamPlaylist(shout_t *shout, const char *fileName)
streamPlaylist(shout_t *shout)
{
const char *song;
char lastSong[PATH_MAX];
if (playlist == NULL) {
if (pezConfig->fileNameIsProgram) {
if ((playlist = playlist_program(fileName)) == NULL)
if (CFG_MEDIA_PROGRAM == cfg_get_media_type()) {
if ((playlist = playlist_program(cfg_get_media_filename())) == NULL)
return (0);
} else {
if ((playlist = playlist_read(fileName)) == NULL)
if ((playlist = playlist_read(cfg_get_media_filename())) == NULL)
return (0);
if (playlist_get_num_items(playlist) == 0)
log_notice("%s: playlist empty", fileName);
log_notice("%s: playlist empty",
cfg_get_media_filename());
}
} else {
/*
@ -959,7 +952,8 @@ streamPlaylist(shout_t *shout, const char *fileName)
playlist_rewind(playlist);
}
if (!pezConfig->fileNameIsProgram && pezConfig->shuffle)
if (CFG_MEDIA_PROGRAM != cfg_get_media_type() &&
cfg_get_media_shuffle())
playlist_shuffle(playlist);
while ((song = playlist_get_next(playlist)) != NULL) {
@ -970,12 +964,12 @@ streamPlaylist(shout_t *shout, const char *fileName)
break;
if (rereadPlaylist) {
rereadPlaylist = rereadPlaylist_notify = 0;
if (pezConfig->fileNameIsProgram)
if (CFG_MEDIA_PROGRAM == cfg_get_media_type())
continue;
log_notice("rereading playlist");
if (!playlist_reread(&playlist))
return (0);
if (pezConfig->shuffle)
if (cfg_get_media_shuffle())
playlist_shuffle(playlist);
else {
playlist_goto_entry(playlist, lastSong);
@ -992,9 +986,12 @@ int
ez_shutdown(int exitval)
{
shout_shutdown();
playlist_shutdown();
freeConfig(pezConfig);
playlist_exit();
cfg_encoder_exit();
cfg_decoder_exit();
log_exit();
cfg_exit();
return (exitval);
}
@ -1003,12 +1000,6 @@ int
main(int argc, char *argv[])
{
int ret;
const char *configFile;
const char *playlistFile;
char *host = NULL;
unsigned short port = 0;
char *mount = NULL;
shout_t *shout;
extern char *optarg;
extern int optind;
@ -1017,38 +1008,15 @@ main(int argc, char *argv[])
unsigned int i;
#endif
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);
log_init();
playlist_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
@ -1058,23 +1026,25 @@ main(int argc, char *argv[])
#ifdef HAVE_STAT
struct stat st;
if (stat(configFile, &st) == -1) {
log_error("%s: %s", configFile, strerror(errno));
if (stat(cfg_get_program_config_file(), &st) == -1) {
log_error("%s: %s", cfg_get_program_config_file(),
strerror(errno));
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",
configFile);
cfg_get_program_config_file());
if (st.st_mode & (S_IWGRP | S_IWOTH)) {
log_error("%s: group and/or world writeable",
configFile);
cfg_get_program_config_file());
return (ez_shutdown(2));
}
#else
FILE *tmp;
if ((tmp = fopen(configFile, "r")) == NULL) {
log_error("%s: %s", configFile, strerror(errno));
if ((tmp = fopen(cfg_get_program_config_file(), "r")) == NULL) {
log_error("%s: %s", cfg_get_program_config_file(),
strerror(errno));
usage();
return (ez_shutdown(2));
}
@ -1082,39 +1052,35 @@ main(int argc, char *argv[])
#endif /* HAVE_STAT */
}
if (!parseConfig(configFile))
if (0 > cfg_reload())
return (ez_shutdown(2));
if (pezConfig->URL == NULL) {
log_error("%s: missing <url>", configFile);
if (!cfg_get_server_hostname() ||
!cfg_get_server_port()){
log_error("%s: missing server configuration",
cfg_get_program_config_file());
return (ez_shutdown(2));
}
if (!urlParse(pezConfig->URL, &host, &port, &mount)) {
log_error("%s: <url>: must be of the form ``http://server:port/mountpoint''",
configFile);
if (!cfg_get_server_password()) {
log_error("%s: <sourcepassword> missing",
cfg_get_program_config_file());
return (ez_shutdown(2));
}
if (pezConfig->password == NULL) {
log_error("%s: <sourcepassword> missing", configFile);
if (!cfg_get_media_filename()) {
log_error("%s: <filename> missing",
cfg_get_program_config_file());
return (ez_shutdown(2));
}
if (pezConfig->fileName == NULL) {
log_error("%s: <filename> missing", configFile);
return (ez_shutdown(2));
}
if (pezConfig->format == NULL) {
if (CFG_STREAM_INVALID == cfg_get_stream_format()) {
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));
if (pezConfig->metadataProgram != NULL)
metadataFromProgram = 1;
else
metadataFromProgram = 0;
#ifdef HAVE_SIGNALS
memset(&act, 0, sizeof(act));
act.sa_handler = sig_handler;
@ -1141,42 +1107,48 @@ main(int argc, char *argv[])
if (shout_open(shout) == SHOUTERR_SUCCESS) {
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 ||
strrcasecmp(pezConfig->fileName, ".m3u") == 0 ||
strrcasecmp(pezConfig->fileName, ".txt") == 0)
if (CFG_MEDIA_PROGRAM == cfg_get_media_type() ||
CFG_MEDIA_PLAYLIST == cfg_get_media_type() ||
(CFG_MEDIA_AUTODETECT == cfg_get_media_type() &&
(strrcasecmp(cfg_get_media_filename(), ".m3u") == 0 ||
strrcasecmp(cfg_get_media_filename(), ".txt") == 0)))
playlistMode = 1;
else
playlistMode = 0;
do {
if (playlistMode) {
cont = streamPlaylist(shout, pezConfig->fileName);
cont = streamPlaylist(shout);
} else {
cont = streamFile(shout, pezConfig->fileName);
cont = streamFile(shout,
cfg_get_media_filename());
}
if (quit)
break;
if (pezConfig->streamOnce)
if (cfg_get_media_stream_once())
break;
} while (cont);
shout_close(shout);
} else
log_error("connection failed: http://%s:%hu%s: %s",
host, port, mount, shout_get_error(shout));
log_error("connection failed: %s://%s:%u%s: %s",
cfg_get_server_protocol_str(), cfg_get_server_hostname(),
cfg_get_server_port(), cfg_get_stream_mountpoint(),
shout_get_error(shout));
if (quit) {
if (cfg_quiet_stderr() && cfg_verbosity())
if (cfg_get_program_quiet_stderr() &&
cfg_get_program_verbosity())
printf("\r");
log_notice("INT or TERM signal received");
}
log_info("exiting");
xfree(host);
xfree(mount);
playlist_free(&playlist);
return (ez_shutdown(0));

View File

@ -62,18 +62,18 @@ _vlog(enum log_levels lvl, const char *fmt, va_list ap)
p = LOG_WARNING;
break;
case NOTICE:
if (cfg_verbosity() < 1)
if (cfg_get_program_verbosity() < 1)
return;
p = LOG_NOTICE;
break;
case INFO:
if (cfg_verbosity() < 2)
if (cfg_get_program_verbosity() < 2)
return;
p = LOG_INFO;
break;
case DEBUG:
default:
if (cfg_verbosity() < 3)
if (cfg_get_program_verbosity() < 3)
return;
p = LOG_DEBUG;
break;
@ -84,11 +84,13 @@ _vlog(enum log_levels lvl, const char *fmt, va_list ap)
va_end(ap2);
}
void
int
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);
return (0);
}
void

View File

@ -28,7 +28,7 @@ enum log_levels {
DEBUG
};
void log_init(void);
int log_init(void);
void log_exit(void);
void log_syserr(enum log_levels, int, const char *);

View File

@ -156,7 +156,7 @@ playlist_run_program(playlist_t *pl)
return ((const char *)pl->prog_track);
}
void
int
playlist_init(void)
{
#ifdef HAVE_RANDOM
@ -168,9 +168,10 @@ playlist_init(void)
#else
srand((unsigned int)time(NULL));
#endif /* HAVE_RANDOM */
return (0);
}
void playlist_shutdown(void) {}
void playlist_exit(void) {}
playlist_t *
playlist_read(const char *filename)

View File

@ -23,12 +23,12 @@ typedef struct playlist playlist_t;
* Initialize the playlist routines. Should be called before any of the other
* playlist functions.
*/
void playlist_init(void);
int playlist_init(void);
/*
* 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

View File

@ -36,7 +36,7 @@
#endif
#include <shout/shout.h>
#include "configfile.h"
#include "cfg.h"
#include "log.h"
#include "util.h"
#include "xalloc.h"
@ -45,8 +45,6 @@
# define BUFSIZ 1024
#endif
extern EZCONFIG *pezConfig;
char * iconvert(const char *, const char *, const char *, int);
int
@ -111,7 +109,7 @@ stream_setup(const char *host, unsigned short port, const char *mount)
shout_free(shout);
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",
shout_get_error(shout));
shout_free(shout);
@ -130,15 +128,15 @@ stream_setup(const char *host, unsigned short port, const char *mount)
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) {
log_error("shout_set_format(MP3): %s",
shout_get_error(shout));
shout_free(shout);
return (NULL);
}
if ((!strcmp(pezConfig->format, VORBIS_FORMAT) ||
!strcmp(pezConfig->format, THEORA_FORMAT)) &&
if ((CFG_STREAM_VORBIS == cfg_get_stream_format() ||
CFG_STREAM_THEORA == cfg_get_stream_format()) &&
shout_set_format(shout, SHOUT_FORMAT_OGG) != SHOUTERR_SUCCESS) {
log_error("shout_set_format(OGG): %s",
shout_get_error(shout));
@ -146,71 +144,70 @@ stream_setup(const char *host, unsigned short port, const char *mount)
return (NULL);
}
if (pezConfig->username &&
shout_set_user(shout, pezConfig->username) != SHOUTERR_SUCCESS) {
if (shout_set_user(shout, cfg_get_server_user()) != SHOUTERR_SUCCESS) {
log_error("shout_set_user: %s",
shout_get_error(shout));
shout_free(shout);
return (NULL);
}
if (pezConfig->serverName &&
shout_set_name(shout, pezConfig->serverName) != SHOUTERR_SUCCESS) {
if (cfg_get_stream_name() &&
shout_set_name(shout, cfg_get_stream_name()) != SHOUTERR_SUCCESS) {
log_error("shout_set_name: %s",
shout_get_error(shout));
shout_free(shout);
return (NULL);
}
if (pezConfig->serverURL &&
shout_set_url(shout, pezConfig->serverURL) != SHOUTERR_SUCCESS) {
if (cfg_get_stream_url() &&
shout_set_url(shout, cfg_get_stream_url()) != SHOUTERR_SUCCESS) {
log_error("shout_set_url: %s",
shout_get_error(shout));
shout_free(shout);
return (NULL);
}
if (pezConfig->serverGenre &&
shout_set_genre(shout, pezConfig->serverGenre) != SHOUTERR_SUCCESS) {
if (cfg_get_stream_genre() &&
shout_set_genre(shout, cfg_get_stream_genre()) != SHOUTERR_SUCCESS) {
log_error("shout_set_genre: %s",
shout_get_error(shout));
shout_free(shout);
return (NULL);
}
if (pezConfig->serverDescription &&
shout_set_description(shout, pezConfig->serverDescription) != SHOUTERR_SUCCESS) {
if (cfg_get_stream_description() &&
shout_set_description(shout, cfg_get_stream_description()) != SHOUTERR_SUCCESS) {
log_error("shout_set_description: %s",
shout_get_error(shout));
shout_free(shout);
return (NULL);
}
if (pezConfig->serverBitrate &&
shout_set_audio_info(shout, SHOUT_AI_BITRATE, pezConfig->serverBitrate) != SHOUTERR_SUCCESS) {
if (cfg_get_stream_bitrate() &&
shout_set_audio_info(shout, SHOUT_AI_BITRATE, cfg_get_stream_bitrate()) != SHOUTERR_SUCCESS) {
log_error("shout_set_audio_info(AI_BITRATE): %s",
shout_get_error(shout));
shout_free(shout);
return (NULL);
}
if (pezConfig->serverChannels &&
shout_set_audio_info(shout, SHOUT_AI_CHANNELS, pezConfig->serverChannels) != SHOUTERR_SUCCESS) {
if (cfg_get_stream_channels() &&
shout_set_audio_info(shout, SHOUT_AI_CHANNELS, cfg_get_stream_channels()) != SHOUTERR_SUCCESS) {
log_error("shout_set_audio_info(AI_CHANNELS): %s",
shout_get_error(shout));
shout_free(shout);
return (NULL);
}
if (pezConfig->serverSamplerate &&
shout_set_audio_info(shout, SHOUT_AI_SAMPLERATE, pezConfig->serverSamplerate) != SHOUTERR_SUCCESS) {
if (cfg_get_stream_samplerate() &&
shout_set_audio_info(shout, SHOUT_AI_SAMPLERATE, cfg_get_stream_samplerate()) != SHOUTERR_SUCCESS) {
log_error("shout_set_audio_info(AI_SAMPLERATE): %s",
shout_get_error(shout));
shout_free(shout);
return (NULL);
}
if (pezConfig->serverQuality &&
shout_set_audio_info(shout, SHOUT_AI_QUALITY, pezConfig->serverQuality) != SHOUTERR_SUCCESS) {
if (cfg_get_stream_quality() &&
shout_set_audio_info(shout, SHOUT_AI_QUALITY, cfg_get_stream_quality()) != SHOUTERR_SUCCESS) {
log_error("shout_set_audio_info(AI_QUALITY): %s",
shout_get_error(shout));
shout_free(shout);
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",
shout_get_error(shout));
shout_free(shout);

View File

@ -17,6 +17,8 @@
#ifndef __XALLOC_H__
#define __XALLOC_H__
#include <stdlib.h>
#define xmalloc(s) xmalloc_c(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__)