# recursively expanded use the := operator instead of the = operator.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-PREDEFINED = __GNUC__=4 \
- __GNUC_MINOR__=4
+PREDEFINED =
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
# tag can be used to specify a list of macro names that should be expanded. The
NEWS
====
+------------------------------------------
+0.7.2 (to be announced) "optical friction"
+------------------------------------------
+
+- A major cleanup of the audio file selector.
+- The client no longer prints error messages from afs commands to
+ stdout but to stderr.
+- The sleep subcommand of para_mixer gained two options to control
+ the startup mood and the time period before fade-out starts. A bunch
+ of further improvements for this subcommand went in as well.
+- Minor cleanup of the net subsystem.
+- The openssl specific code now employs the EVP API to compute hashes.
+ It should compile without warnings against openssl-3.
+
+Downloads:
+[tarball](./releases/paraslash-git.tar.xz)
+
--------------------------------------
0.7.1 (2022-10-03) "digital spindrift"
--------------------------------------
requires support from the compiler, the oldest supported gcc version
has been bumped to gcc-5.4 (released in 2015).
+Downloads:
[tarball](./releases/paraslash-0.7.1.tar.xz),
[signature](./releases/paraslash-0.7.1.tar.xz.asc)
[tarball](./releases/paraslash-0.6.1.tar.xz),
[signature](./releases/paraslash-0.6.1.tar.xz.asc)
+---------------------------------------
+0.5.9 (2021-11-04) "reversed dimension"
+---------------------------------------
+This release contains a few important fixes which have accumulated in
+the maint branch. The paraslash-0.5.x series has now reached its end
+of life and will no longer be supported. All users should upgrade to
+a more recent version at this point.
+
+- Fix an issue with the bash completion script.
+- Initialize the random seed also when using libgrypt.
+- Fix some compiler warnings in the resample filter
+- Don't return spurious errors from the ff server command.
+
+Downloads:
+[tarball](./releases/paraslash-0.5.9.tar.bz2),
+[signature](./releases/paraslash-0.5.9.tar.bz2.asc)
+
---------------------------------------
0.5.8 (2017-09-23) "branching parabola"
---------------------------------------
#include "sched.h"
#include "fd.h"
#include "signal.h"
-#include "mood.h"
#include "sideband.h"
#include "command.h"
-/** The osl tables used by afs. \sa \ref blob.c. */
-enum afs_table_num {
- /** Contains audio file information. See \ref aft.c. */
- TBLNUM_AUDIO_FILES,
- /** The table for the paraslash attributes. See \ref attribute.c. */
- TBLNUM_ATTRIBUTES,
- /*
- * Moods and playlists organize the current set of admissible files in
- * an osl table which contains only volatile columns. Each row consists
- * of a pointer to an audio file and the score value of this file.
- */
- TBLNUM_SCORES,
- /**
- * A standard blob table containing the mood definitions. For details
- * see \ref mood.c.
- */
- TBLNUM_MOODS,
- /** A blob table containing lyrics on a per-song basis. */
- TBLNUM_LYRICS,
- /** Another blob table for images (for example album cover art). */
- TBLNUM_IMAGES,
- /** Yet another blob table for storing standard playlists. */
- TBLNUM_PLAYLIST,
- /** How many tables are in use? */
- NUM_AFS_TABLES
-};
-
-static struct afs_table afs_tables[NUM_AFS_TABLES] = {
- [TBLNUM_AUDIO_FILES] = {.init = aft_init, .name = "audio_files"},
- [TBLNUM_ATTRIBUTES] = {.init = attribute_init, .name = "attributes"},
- [TBLNUM_SCORES] = {.init = score_init, .name = "scores"},
- [TBLNUM_MOODS] = {.init = moods_init, .name = "moods"},
- [TBLNUM_LYRICS] = {.init = lyrics_init, .name = "lyrics"},
- [TBLNUM_IMAGES] = {.init = images_init, .name = "images"},
- [TBLNUM_PLAYLIST] = {.init = playlists_init, .name = "playlists"},
+/**
+ * The array of tables of the audio file selector.
+ *
+ * We organize them in an array to be able to loop over all tables.
+ */
+static const struct afs_table {
+ /** The name is no table operation, so define it here. */
+ const char * const name;
+ /** The only way to invoke the ops is via this pointer. */
+ const struct afs_table_operations *ops;
+} afs_tables[] = {
+ {.name = "audio_files", .ops = &aft_ops},
+ {.name = "attributes", .ops = &attr_ops},
+ {.name = "scores", .ops = &score_ops},
+ {.name = "moods", .ops = &moods_ops},
+ {.name = "lyrics", .ops = &lyrics_ops},
+ {.name = "images", .ops = &images_ops},
+ {.name = "playlists", .ops = &playlists_ops},
};
+/** Used to loop over the afs tables. */
+#define NUM_AFS_TABLES ARRAY_SIZE(afs_tables)
struct command_task {
/** The file descriptor for the local socket. */
*/
struct callback_query {
/** The function to be called. */
- afs_callback *handler;
+ afs_callback *cb;
/** The number of bytes of the query */
size_t query_size;
};
if (ret < 0)
goto out;
cq = query_shm;
- cq->handler = f;
+ cq->cb = f;
cq->query_size = query_shm_size - sizeof(*cq);
if (query)
return write_all(server_socket, buf, 8);
}
-static int activate_mood_or_playlist(const char *arg, int *num_admissible,
- char **errmsg)
+static int activate_mood_or_playlist(const char *arg, struct para_buffer *pb)
{
enum play_mode mode;
int ret;
+ char *msg;
if (!arg) {
+ ret = mood_load(NULL, &msg);
+ mode = PLAY_MODE_MOOD;
+ } else if (!strncmp(arg, "p/", 2)) {
+ ret = playlist_load(arg + 2, &msg);
+ mode = PLAY_MODE_PLAYLIST;
+ } else if (!strncmp(arg, "m/", 2)) {
+ ret = mood_load(arg + 2, &msg);
mode = PLAY_MODE_MOOD;
- ret = change_current_mood(NULL, errmsg);
- if (ret < 0) {
- if (num_admissible)
- *num_admissible = 0;
- return ret;
- }
} else {
- if (!strncmp(arg, "p/", 2)) {
- ret = playlist_open(arg + 2);
- if (ret < 0 && errmsg)
- *errmsg = make_message( "could not open %s",
- arg);
- mode = PLAY_MODE_PLAYLIST;
- } else if (!strncmp(arg, "m/", 2)) {
- ret = change_current_mood(arg + 2, errmsg);
- mode = PLAY_MODE_MOOD;
- } else {
- if (errmsg)
- *errmsg = make_message("%s: parse error", arg);
- return -ERRNO_TO_PARA_ERROR(EINVAL);
- }
- if (ret < 0)
- return ret;
+ ret = -ERRNO_TO_PARA_ERROR(EINVAL);
+ msg = make_message("%s: parse error", arg);
}
- if (num_admissible)
- *num_admissible = ret;
+ if (pb)
+ para_printf(pb, "%s", msg);
+ free(msg);
+ if (ret < 0)
+ return ret;
current_play_mode = mode;
/*
* We get called with arg == current_mop from the signal dispatcher
*/
if (arg != current_mop) {
free(current_mop);
- if (arg) {
- current_mop = para_strdup(arg);
- mutex_lock(mmd_mutex);
- strncpy(mmd->afs_mode_string, arg,
- sizeof(mmd->afs_mode_string));
- mmd->afs_mode_string[sizeof(mmd->afs_mode_string) - 1] = '\0';
- mmd->events++;
- mutex_unlock(mmd_mutex);
- } else {
- mutex_lock(mmd_mutex);
- strcpy(mmd->afs_mode_string, "dummy");
- mmd->events++;
- mutex_unlock(mmd_mutex);
- current_mop = NULL;
- }
+ current_mop = arg? para_strdup(arg) : NULL;
}
+ /* Notify the server about the mood/playlist change. */
+ mutex_lock(mmd_mutex);
+ strncpy(mmd->afs_mode_string, arg? arg: "dummy",
+ sizeof(mmd->afs_mode_string));
+ mmd->afs_mode_string[sizeof(mmd->afs_mode_string) - 1] = '\0';
+ mmd->events++;
+ mutex_unlock(mmd_mutex);
return 1;
}
free(pb->buf);
}
-static int com_select_callback(struct afs_callback_arg *aca)
-{
- const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT);
- const char *arg;
- int num_admissible, ret;
- char *errmsg;
-
- ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr);
- assert(ret >= 0);
- arg = lls_input(0, aca->lpr);
- ret = clear_score_table();
- if (ret < 0) {
- para_printf(&aca->pbout, "could not clear score table\n");
- goto free_lpr;
- }
- if (current_play_mode == PLAY_MODE_MOOD)
- close_current_mood();
- else
- playlist_close();
- ret = activate_mood_or_playlist(arg, &num_admissible, &errmsg);
- if (ret >= 0)
- goto out;
- /* ignore subsequent errors (but log them) */
- para_printf(&aca->pbout, "%s\n", errmsg);
- free(errmsg);
- para_printf(&aca->pbout, "could not activate %s\n", arg);
- if (current_mop && strcmp(current_mop, arg) != 0) {
- int ret2;
- para_printf(&aca->pbout, "switching back to %s\n", current_mop);
- ret2 = activate_mood_or_playlist(current_mop, &num_admissible,
- &errmsg);
- if (ret2 >= 0)
- goto out;
- para_printf(&aca->pbout, "%s\n", errmsg);
- free(errmsg);
- para_printf(&aca->pbout, "could not reactivate %s: %s\n",
- current_mop, para_strerror(-ret2));
- }
- para_printf(&aca->pbout, "activating dummy mood\n");
- activate_mood_or_playlist(NULL, &num_admissible, NULL);
-out:
- para_printf(&aca->pbout, "activated %s (%d admissible file%s)\n",
- current_mop? current_mop : "dummy mood", num_admissible,
- num_admissible == 1? "" : "s");
-free_lpr:
- lls_free_parse_result(aca->lpr, cmd);
- return ret;
-}
-
-static int com_select(struct command_context *cc, struct lls_parse_result *lpr)
-{
- const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT);
- char *errctx;
- int ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx));
-
- if (ret < 0) {
- send_errctx(cc, errctx);
- return ret;
- }
- return send_lls_callback_request(com_select_callback, cmd, lpr, cc);
-}
-EXPORT_SERVER_CMD_HANDLER(select);
-
static void init_admissible_files(const char *arg)
{
- int ret = activate_mood_or_playlist(arg, NULL, NULL);
+ int ret = activate_mood_or_playlist(arg, NULL);
if (ret < 0) {
PARA_WARNING_LOG("could not activate %s: %s\n", arg,
para_strerror(-ret));
if (arg)
- activate_mood_or_playlist(NULL, NULL, NULL);
+ activate_mood_or_playlist(NULL, NULL);
}
}
int i;
PARA_NOTICE_LOG("closing afs tables\n");
for (i = 0; i < NUM_AFS_TABLES; i++)
- afs_tables[i].close();
+ afs_tables[i].ops->close();
free(database_dir);
database_dir = NULL;
}
int i, ret;
get_database_dir();
- PARA_NOTICE_LOG("opening %d osl tables in %s\n", NUM_AFS_TABLES,
+ PARA_NOTICE_LOG("opening %zu osl tables in %s\n", NUM_AFS_TABLES,
database_dir);
for (i = 0; i < NUM_AFS_TABLES; i++) {
- ret = afs_tables[i].open(database_dir);
+ ret = afs_tables[i].ops->open(database_dir);
if (ret >= 0)
continue;
PARA_ERROR_LOG("could not open %s\n", afs_tables[i].name);
if (ret >= 0)
return ret;
while (i)
- afs_tables[--i].close();
+ afs_tables[--i].ops->close();
return ret;
}
return ret;
}
+/**
+ * Format and send an error message to the command handler.
+ *
+ * To pass an error message from the callback of an afs command to the client,
+ * this function should be called. It formats the message into a buffer which
+ * is passed as a shared memory area to the command handler from where it
+ * propagates to the client.
+ *
+ * The message will be tagged with the ERROR_LOG sideband designator so that
+ * the client writes it to its stderr stream rather than to stdout as with
+ * aca->pbout. In analogy to the default Unix semantics of stderr, the message
+ * is sent without buffering.
+ *
+ * If sending the error message fails, an error is logged on the server side,
+ * but no other action is taken.
+ *
+ * \param aca Used to obtain the fd to send the shmid to.
+ * \param fmt Usual format string.
+ */
+__printf_2_3 void afs_error(const struct afs_callback_arg *aca,
+ const char *fmt,...)
+{
+ va_list argp;
+ char *msg;
+ unsigned n;
+ int ret;
+
+ va_start(argp, fmt);
+ n = xvasprintf(&msg, fmt, argp);
+ va_end(argp);
+ ret = pass_buffer_as_shm(aca->fd, SBD_ERROR_LOG, msg, n + 1);
+ if (ret < 0)
+ PARA_ERROR_LOG("Could not send %s: %s\n", msg,
+ para_strerror(-ret));
+ free(msg);
+}
+
static int call_callback(int fd, int query_shmid)
{
void *query_shm;
.fd = fd,
.band = SBD_OUTPUT
};
- ret = cq->handler(&aca);
+ ret = cq->cb(&aca);
ret2 = shm_detach(query_shm);
if (ret2 < 0) {
if (ret < 0) /* ignore (but log) detach error */
__noreturn void afs_init(int socket_fd)
{
static struct sched s;
- int i, ret;
+ int ret;
register_signal_task(&s);
init_list_head(&afs_client_list);
- for (i = 0; i < NUM_AFS_TABLES; i++)
- afs_tables[i].init(&afs_tables[i]);
ret = open_afs_tables();
if (ret < 0)
goto out;
}
ret = schedule(&s);
sched_shutdown(&s);
- close_current_mood();
+ mood_unload();
out_close:
close_afs_tables();
out:
exit(EXIT_FAILURE);
}
+static int com_select_callback(struct afs_callback_arg *aca)
+{
+ const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT);
+ const char *arg;
+ int ret;
+
+ ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr);
+ assert(ret >= 0);
+ arg = lls_input(0, aca->lpr);
+ score_clear();
+ if (current_play_mode == PLAY_MODE_MOOD)
+ mood_unload();
+ else
+ playlist_unload();
+ ret = activate_mood_or_playlist(arg, &aca->pbout);
+ if (ret >= 0)
+ goto free_lpr;
+ /* ignore subsequent errors (but log them) */
+ if (current_mop && strcmp(current_mop, arg) != 0) {
+ int ret2;
+ afs_error(aca, "switching back to %s\n", current_mop);
+ ret2 = activate_mood_or_playlist(current_mop, &aca->pbout);
+ if (ret2 >= 0)
+ goto free_lpr;
+ afs_error(aca, "could not reactivate %s: %s\n", current_mop,
+ para_strerror(-ret2));
+ }
+ activate_mood_or_playlist(NULL, &aca->pbout);
+free_lpr:
+ lls_free_parse_result(aca->lpr, cmd);
+ return ret;
+}
+
+static int com_select(struct command_context *cc, struct lls_parse_result *lpr)
+{
+ const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT);
+ char *errctx;
+ int ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx));
+
+ if (ret < 0) {
+ send_errctx(cc, errctx);
+ return ret;
+ }
+ return send_lls_callback_request(com_select_callback, cmd, lpr, cc);
+}
+EXPORT_SERVER_CMD_HANDLER(select);
+
static int com_init_callback(struct afs_callback_arg *aca)
{
uint32_t table_mask = *(uint32_t *)aca->query.data;
close_afs_tables();
get_database_dir();
for (i = 0; i < NUM_AFS_TABLES; i++) {
- struct afs_table *t = &afs_tables[i];
+ const struct afs_table *t = afs_tables + i;
if (!(table_mask & (1 << i)))
continue;
- if (!t->create)
+ if (!t->ops->create)
continue;
- ret = t->create(database_dir);
+ ret = t->ops->create(database_dir);
if (ret < 0) {
- para_printf(&aca->pbout, "cannot create table %s\n",
- t->name);
+ afs_error(aca, "cannot create table %s\n", t->name);
goto out;
}
para_printf(&aca->pbout, "successfully created %s table\n",
}
ret = open_afs_tables();
if (ret < 0)
- para_printf(&aca->pbout, "cannot open afs tables: %s\n",
+ afs_error(aca, "cannot open afs tables: %s\n",
para_strerror(-ret));
out:
return ret;
table_mask = 0;
for (i = 0; i < num_inputs; i++) {
for (j = 0; j < NUM_AFS_TABLES; j++) {
- struct afs_table *t = &afs_tables[j];
+ const struct afs_table *t = afs_tables + j;
if (strcmp(lls_input(i, lpr), t->name))
continue;
int i, ret;
for (i = 0; i < NUM_AFS_TABLES; i++) {
- struct afs_table *t = &afs_tables[i];
- if (!t->event_handler)
+ const struct afs_table *t = afs_tables + i;
+ if (!t->ops->event_handler)
continue;
- ret = t->event_handler(event, pb, data);
+ ret = t->ops->event_handler(event, pb, data);
if (ret < 0) {
PARA_CRIT_LOG("table %s, event %u: %s\n", t->name,
event, para_strerror(-ret));
struct afs_info *old_afsi;
};
-/** Function pointers for table handling. */
-struct afs_table {
- /** Initializes the other pointers in this struct. */
- void (*init)(struct afs_table *t);
- /** The name of this table. */
- const char *name;
- /** Gets called on startup and on \p SIGHUP. */
+/** Methods for table startup/shutdown and event handling. */
+struct afs_table_operations {
+ /** Gets called on startup and on SIGHUP. */
int (*open)(const char *base_dir);
- /** Gets called on shutdown and on \p SIGHUP. */
+ /** Gets called on shutdown and on SIGHUP. */
void (*close)(void);
- /** Called by the \a init afs command. */
+ /** Called from the init command. */
int (*create)(const char *);
- /** Handles afs events. */
+ /** Handle events generated by other tables. See enum \ref afs_events. */
int (*event_handler)(enum afs_events event, struct para_buffer *pb,
void *data);
};
};
/**
+ * The "top half" of an afs command.
+ *
* Afs command handlers run as a process which is not related to the afs
* process, i.e. they can not change the address space of afs directly.
* Therefore afs commands typically consist of two functions: The command
typedef int afs_callback(struct afs_callback_arg *aca);
/**
+ * Dispatch the output of an afs callback.
+ *
* Some AFS callbacks need to send data back to the command handler. Pointers
* to this type of function are passed to \ref send_callback_request() and
- * related functions to dispatch the data in the command handler process.
+ * related functions to dispatch the data in the command handler process. Most
+ * (but not all) afs commands pass \ref afs_cb_result_handler(), which sends
+ * the output of the callback to the connected client.
*/
typedef int callback_result_handler(struct osl_object *result, uint8_t band, void *private);
int afs_cb_result_handler(struct osl_object *result, uint8_t band, void *private);
int send_lls_callback_request(afs_callback *f,
const struct lls_command * const cmd,
struct lls_parse_result *lpr, void *private_result_data);
+__printf_2_3 void afs_error(const struct afs_callback_arg *aca,
+ const char *fmt,...);
int string_compare(const struct osl_object *obj1, const struct osl_object *obj2);
int for_each_matching_row(struct pattern_match_data *pmd);
/* score */
-void score_init(struct afs_table *t);
-int admissible_file_loop(void *data, osl_rbtree_loop_func *func);
+extern const struct afs_table_operations score_ops;
+int score_loop(osl_rbtree_loop_func *func, void *data);
int score_get_best(struct osl_row **aft_row, long *score);
int get_score_and_aft_row(struct osl_row *score_row, long *score, struct osl_row **aft_row);
int score_add(const struct osl_row *row, long score);
int score_update(const struct osl_row *aft_row, long new_score);
-int get_num_admissible_files(unsigned *num);
int score_delete(const struct osl_row *aft_row);
-int clear_score_table(void);
-int row_belongs_to_score_table(const struct osl_row *aft_row, unsigned *rank);
+void score_clear(void);
+bool row_belongs_to_score_table(const struct osl_row *aft_row);
/* attribute */
-void attribute_init(struct afs_table *t);
+extern const struct afs_table_operations attr_ops;
void get_attribute_bitmap(const uint64_t *atts, char *buf); /* needed by com_ls() */
int get_attribute_bitnum_by_name(const char *att_name, unsigned char *bitnum);
int get_attribute_text(uint64_t *atts, const char *delim, char **text);
int attribute_check_callback(struct afs_callback_arg *aca);
/* aft */
-void aft_init(struct afs_table *t);
+extern const struct afs_table_operations aft_ops;
int aft_get_row_of_path(const char *path, struct osl_row **row);
int aft_check_attributes(uint64_t att_mask, struct para_buffer *pb);
int open_and_update_audio_file(int *fd);
int aft_check_callback(struct afs_callback_arg *aca);
void free_status_items(void);
+/* mood */
+int mood_load(const char *mood_name, char **msg);
+void mood_unload(void);
+int mood_check_callback(struct afs_callback_arg *aca);
+
/* playlist */
-int playlist_open(const char *name);
-void playlist_close(void);
+int playlist_load(const char *name, char **msg);
+void playlist_unload(void);
int playlist_check_callback(struct afs_callback_arg *aca);
/** evaluates to 1 if x < y, to -1 if x > y and to 0 if x == y */
/** Define exported functions and a table pointer for an osl blob table. */
#define DECLARE_BLOB_SYMBOLS(table_name, cmd_prefix) \
- void table_name ## _init(struct afs_table *t); \
int cmd_prefix ## _get_name_by_id(uint32_t id, char **name); \
int cmd_prefix ## _get_def_by_id(uint32_t id, struct osl_object *def); \
- int cmd_prefix ## _get_def_by_name(char *name, struct osl_object *def); \
+ int cmd_prefix ## _get_def_by_name(const char *name, struct osl_object *def); \
int cmd_prefix ## _get_name_and_def_by_row(const struct osl_row *row, \
char **name, struct osl_object *def); \
int table_name ##_event_handler(enum afs_events event, \
struct para_buffer *pb, void *data); \
- extern struct osl_table *table_name ## _table;
+ extern struct osl_table *table_name ## _table; \
+ extern const struct afs_table_operations table_name ## _ops;
/** \cond blob_symbols */
DECLARE_BLOB_SYMBOLS(lyrics, lyr);
aca->pbout.flags = (opts->mode == LS_MODE_PARSER)? PBF_SIZE_PREFIX : 0;
if (admissible_only(opts))
- ret = admissible_file_loop(opts, prepare_ls_row);
+ ret = score_loop(prepare_ls_row, opts);
else
ret = osl(osl_rbtree_loop(audio_file_table, AFTCOL_PATH, opts,
prepare_ls_row));
ret = afs_event(AUDIO_FILE_ADD, &aca->pbout, aft_row);
out:
if (ret < 0)
- para_printf(&aca->pbout, "could not add %s\n", path);
+ afs_error(aca, "could not add %s\n", path);
lls_free_parse_result(aca->lpr, cmd);
return ret;
}
ret = get_afsi_object_of_row(row, &obj);
if (ret < 0) {
- para_printf(&aca->pbout, "cannot touch %s\n", name);
+ afs_error(aca, "cannot touch %s\n", name);
return ret;
}
ret = load_afsi(&old_afsi, &obj);
if (ret < 0) {
- para_printf(&aca->pbout, "cannot touch %s\n", name);
+ afs_error(aca, "cannot touch %s\n", name);
return ret;
}
new_afsi = old_afsi;
uint32_t id = lls_uint32_val(0, r_i);
ret = img_get_name_by_id(id, NULL);
if (ret < 0) {
- para_printf(&aca->pbout, "invalid image ID: %u\n", id);
+ afs_error(aca, "invalid image ID: %u\n", id);
return ret;
}
}
uint32_t id = lls_uint32_val(0, r_y);
ret = lyr_get_name_by_id(id, NULL);
if (ret < 0) {
- para_printf(&aca->pbout, "invalid lyrics ID: %u\n", id);
+ afs_error(aca, "invalid lyrics ID: %u\n", id);
return ret;
}
}
return ret;
ret = osl(osl_del_row(audio_file_table, row));
if (ret < 0)
- para_printf(&aca->pbout, "cannot remove %s\n", name);
+ afs_error(aca, "cannot remove %s\n", name);
return ret;
}
ret = get_attribute_bitnum_by_name(p, &bitnum);
free(p);
if (ret < 0) {
- para_printf(&aca->pbout, "invalid argument: %s\n", arg);
+ afs_error(aca, "invalid argument: %s\n", arg);
goto out;
}
if (c == '+')
}
}
-/**
- * Initialize the audio file table.
- *
- * \param t Pointer to the structure to be initialized.
- */
-void aft_init(struct afs_table *t)
-{
- t->open = aft_open;
- t->close = aft_close;
- t->create = aft_create;
- t->event_handler = aft_event_handler;
-}
+/** The audio file table contains information about known audio files. */
+const struct afs_table_operations aft_ops = {
+ .open = aft_open,
+ .close = aft_close,
+ .create = aft_create,
+ .event_handler = aft_event_handler,
+};
}
ret = osl(osl_get_object(table, row, ATTCOL_BITNUM, &bitnum_obj));
if (ret < 0) {
- para_printf(&aca->pbout, "%s: %s\n", name, para_strerror(-ret));
+ afs_error(aca, "%s: %s\n", name, para_strerror(-ret));
return ret;
}
para_printf(&aca->pbout, "%u\t%s\n", *(unsigned char*)bitnum_obj.data,
}
EXPORT_SERVER_CMD_HANDLER(lsatt);
-struct addatt_event_data {
- const char *name;
- unsigned char bitnum;
-};
-
static int com_addatt_callback(struct afs_callback_arg *aca)
{
const struct lls_command *cmd = SERVER_CMD_CMD_PTR(ADDATT);
struct osl_object objs[NUM_ATT_COLUMNS];
struct osl_row *row;
unsigned char bitnum;
- struct addatt_event_data aed;
len = strlen(name);
if (len == 0 || name[len - 1] == '-' || name[len - 1] == '+') {
- para_printf(&aca->pbout,
- "invalid attribute name: %s\n", name);
+ afs_error(aca, "invalid attribute name: %s\n", name);
continue;
}
ret = get_attribute_bitnum_by_name(name, &bitnum);
ret = osl(osl_add_row(attribute_table, objs));
if (ret < 0)
goto out;
- aed.name = name;
- aed.bitnum = bitnum;
- ret = afs_event(ATTRIBUTE_ADD, &aca->pbout, &aed);
+ ret = afs_event(ATTRIBUTE_ADD, &aca->pbout, NULL);
if (ret < 0)
goto out;
greatest_att_bitnum = PARA_MAX(greatest_att_bitnum, (int)bitnum);
}
out:
if (ret < 0)
- para_printf(&aca->pbout, "error while adding %s\n",
+ afs_error(aca, "error while adding %s\n",
lls_input(i, aca->lpr));
lls_free_parse_result(aca->lpr, cmd);
return ret;
ret = osl(osl_update_object(attribute_table, row, ATTCOL_NAME, &obj));
out:
if (ret < 0)
- para_printf(&aca->pbout, "cannot rename %s to %s\n", old, new);
+ afs_error(aca, "cannot rename %s to %s\n", old, new);
else
ret = afs_event(ATTRIBUTE_RENAME, &aca->pbout, NULL);
lls_free_parse_result(aca->lpr, cmd);
ret = get_attribute_bitnum_by_name(name, &red.bitnum);
if (ret < 0) {
- para_printf(&aca->pbout, "cannot remove %s\n", name);
+ afs_error(aca, "cannot remove %s\n", name);
return ret;
}
para_printf(&aca->pbout, "removing attribute %s\n", name);
ret = osl(osl_del_row(table, row));
if (ret < 0) {
- para_printf(&aca->pbout, "cannot remove %s\n", name);
+ afs_error(aca, "cannot remove %s\n", name);
return ret;
}
return afs_event(ATTRIBUTE_REMOVE, &aca->pbout, &red);
return osl(osl_create_table(&attribute_table_desc));
}
-/**
- * Initialize the attribute table structure.
- *
- * \param t The table structure to initialize.
- */
-void attribute_init(struct afs_table *t)
-{
- t->open = attribute_open;
- t->close = attribute_close;
- t->create = attribute_create;
-}
+/** The attribute table stores name/bitnum pairs. */
+const struct afs_table_operations attr_ops = { /* no event handler */
+ .open = attribute_open,
+ .close = attribute_close,
+ .create = attribute_create,
+};
}
ret = osl(osl_get_object(table, row, BLOBCOL_ID, &obj));
if (ret < 0) {
- para_printf(&aca->pbout, "cannot list %s\n", name);
+ afs_error(aca, "cannot list %s\n", name);
return ret;
}
id = read_u32(obj.data);
int ret = osl(osl_del_row(table, row));
if (ret < 0) {
- para_printf(&aca->pbout, "cannot remove %s\n", name);
+ afs_error(aca, "cannot remove %s\n", name);
return ret;
}
return 1;
ret = afs_event(BLOB_ADD, NULL, table);
out:
if (ret < 0)
- para_printf(&aca->pbout, "cannot add %s\n", name);
+ afs_error(aca, "cannot add %s\n", name);
else
para_printf(&aca->pbout, "added %s as id %u\n", name, id);
return ret;
ret = osl(osl_get_row(table, BLOBCOL_NAME, &obj, &row));
if (ret < 0) {
- para_printf(&aca->pbout, "cannot find source blob %s\n", src);
+ afs_error(aca, "cannot find source blob %s\n", src);
goto out;
}
obj.data = (char *)dest;
obj.size = strlen(dest) + 1;
ret = osl(osl_update_object(table, row, BLOBCOL_NAME, &obj));
if (ret < 0) {
- para_printf(&aca->pbout, "cannot rename blob %s to %s\n",
- src, dest);
+ afs_error(aca, "cannot rename blob %s to %s\n", src, dest);
goto out;
}
ret = afs_event(BLOB_RENAME, NULL, table);
return blob_get_name_by_id(table_name ## _table, id, name); \
}
-static int blob_get_def_by_name(struct osl_table *table, char *name,
+static int blob_get_def_by_name(struct osl_table *table, const char *name,
struct osl_object *def)
{
struct osl_row *row;
- struct osl_object obj = {.data = name, .size = strlen(name) + 1};
+ struct osl_object obj = {.data = (void *)name, .size = strlen(name) + 1};
int ret;
def->data = NULL;
/** Define the \p get_def_by_id function for this blob type. */
#define DEFINE_GET_DEF_BY_NAME(table_name, cmd_prefix) \
- int cmd_prefix ## _get_def_by_name(char *name, struct osl_object *def) \
+ int cmd_prefix ## _get_def_by_name(const char *name, struct osl_object *def) \
{ \
return blob_get_def_by_name(table_name ## _table, name, def); \
}
&table_name ## _table_desc, dir); \
}
-
-/** Define the \p init function for this blob type. */
-#define DEFINE_BLOB_INIT(table_name) \
- void table_name ## _init(struct afs_table *t) \
- { \
- t->open = table_name ## _open; \
- t->close = table_name ## _close; \
- t->create = table_name ## _create;\
- t->event_handler = table_name ##_event_handler; \
- table_name ## _table = NULL; \
- }
-
+/** Blob tables map integers to blobs. */
+#define DEFINE_BLOB_AFS_TABLE_OPS(table_name) \
+ const struct afs_table_operations table_name ## _ops = { \
+ .open = table_name ## _open, \
+ .close = table_name ## _close, \
+ .create = table_name ## _create, \
+ .event_handler = table_name ##_event_handler, \
+ };
/** Define all functions for this blob type. */
#define DEFINE_BLOB_FUNCTIONS(table_name, short_name, c_short_name) \
DEFINE_BLOB_OPEN(table_name) \
DEFINE_BLOB_CLOSE(table_name) \
DEFINE_BLOB_CREATE(table_name) \
- DEFINE_BLOB_INIT(table_name) \
+ DEFINE_BLOB_AFS_TABLE_OPS(table_name) \
DEFINE_BLOB_COMMAND(ls, LS, table_name, short_name, c_short_name) \
DEFINE_BLOB_COMMAND(cat, CAT, table_name, short_name, c_short_name) \
DEFINE_BLOB_COMMAND(add, ADD, table_name, short_name, c_short_name) \
PARA_NOTICE_LOG("connecting %s:%u\n", host, port);
ct->scc.fd = -1;
- ret = para_connect_simple(IPPROTO_TCP, host, port);
+ ret = para_connect(IPPROTO_TCP, host, port);
if (ret < 0)
return ret;
ct->scc.fd = ret;
int send_afs_status(struct command_context *cc, int parser_friendly);
static bool subcmd_should_die;
+/*
+ * Don't call PARA_XXX_LOG() here as we might already hold the log mutex. See
+ * generic_signal_handler() for details.
+ */
static void command_handler_sighandler(int s)
{
- if (s != SIGTERM)
- return;
- PARA_EMERG_LOG("terminating on signal %d\n", SIGTERM);
- subcmd_should_die = true;
+ if (s == SIGTERM)
+ subcmd_should_die = true;
}
/*
* while we sleep.
*/
para_block_signal(SIGTERM);
+ para_block_signal(SIGUSR1);
for (;;) {
sigset_t set;
/*
* open a race window similar to the one described above.
*/
pselect(1, NULL, NULL, NULL, &ts, &set);
- if (subcmd_should_die)
+ if (subcmd_should_die) {
+ PARA_EMERG_LOG("terminating on SIGTERM\n");
goto out;
+ }
ret = -E_SERVER_CRASH;
if (getppid() == 1)
goto out;
#include "net.h"
#include "fd.h"
+#ifndef DCCP_SOCKOPT_CCID
+#define DCCP_SOCKOPT_CCID 13 /**< Sets both TX/RX CCID. */
+#endif
+
static void dccp_recv_close(struct receiver_node *rn)
{
if (rn->fd > 0)
return 1;
}
+/** Flowopt shortcut */
+#define OPT_ADD(fo, lev, opt, val, len) flowopt_add(fo, lev, opt, #opt, val, len)
+
static int dccp_recv_open(struct receiver_node *rn)
{
struct lls_parse_result *lpr = rn->lpr;
OPT_ADD(fo, SOL_DCCP, DCCP_SOCKOPT_CCID, ccids, i);
}
- fd = makesock(IPPROTO_DCCP, 0, host, port, fo);
+ fd = makesock(IPPROTO_DCCP, false, host, port, fo);
flowopt_cleanup(fo);
free(ccids);
if (fd < 0)
sched_monitor_readfd(dss->listen_fds[n], s);
}
+#ifndef DCCP_SOCKOPT_TX_CCID
+#define DCCP_SOCKOPT_TX_CCID 14 /**< Set/get the TX CCID. */
+#endif
+
/**
* Query the TX CCID used on the sender-client half connection.
* \param sockfd Server socket descriptor to query (after accept(2)).
free_sender_status(dss);
}
+#ifndef DCCP_SOCKOPT_GET_CUR_MPS
+#define DCCP_SOCKOPT_GET_CUR_MPS 5 /**< Max packet size, RFC 4340, 14. */
+#endif
+
+/** Estimated worst-case length of a DCCP header including options. */
+#define DCCP_MAX_HEADER 128
+
/** * Obtain current MPS according to RFC 4340, sec. 14. */
static int dccp_init_fec(struct sender_client *sc)
{
/** \file error.h List of error codes and messages. */
+/** \cond para_error */
/** Codes and messages. */
#define PARA_ERRORS \
PARA_ERROR(SUCCESS, "success"), \
PARA_ERROR(NO_AUDIO_FILE, "no audio file"), \
PARA_ERROR(NOFD, "did not receive open fd from afs"), \
PARA_ERROR(NO_MATCH, "no matches"), \
- PARA_ERROR(NO_MOOD, "no mood available"), \
PARA_ERROR(NO_MORE_SLOTS, "no more empty slots"), \
PARA_ERROR(NOT_PLAYING, "not playing"), \
PARA_ERROR(NO_VALID_FILES, "no valid file found in playlist"), \
PARA_ERROR(OPUS_SET_GAIN, "opus: could not set gain"), \
PARA_ERROR(PATH_FOUND, ""), /* not really an error */ \
PARA_ERROR(PLAYLIST_EMPTY, "attempted to load empty playlist"), \
- PARA_ERROR(PLAYLIST_LOADED, ""), /* not really an error */ \
PARA_ERROR(PREBUFFER_SUCCESS, "prebuffering complete"), \
PARA_ERROR(PRIVATE_KEY, "can not read private key"), \
PARA_ERROR(QUEUE, "packet queue overrun"), \
extern const char * const para_errlist[];
/** Exactly one .c file per executable must define the array. */
#define DEFINE_PARA_ERRLIST const char * const para_errlist[] = {PARA_ERRORS}
+/** \endcond para_error */
/**
* This bit indicates whether a number is considered a system error number
struct lls_parse_result *lpr = rn->lpr;
const char *r_i = RECV_CMD_OPT_STRING_VAL(HTTP, HOST, lpr);
uint32_t r_p = RECV_CMD_OPT_UINT32_VAL(HTTP, PORT, lpr);
- int fd, ret = para_connect_simple(IPPROTO_TCP, r_i, r_p);
+ int fd, ret = para_connect(IPPROTO_TCP, r_i, r_p);
if (ret < 0)
return ret;
[subcommand sleep]
purpose = stream, fade out, sleep, fade in
[description]
- Change to the initial volume and select the initial mood/playlist.
- Fade out to the given fade-out volume in the specified time. Switch
- to the sleep mood/playlist and wait until wake time minus fade-in
- time. Finally, switch to the wake mood/playlist and fade in to the
- fade-in volume.
+ Set the initial volume and mood, start playing and sleep. Then switch
+ to the fade-out mood and fade to the fade-out volume. Next, switch to
+ the sleep mood and wait until wake time minus fade-in time. Finally,
+ switch to the wake mood and fade in to the fade-in volume.
[/description]
[option ivol]
summary = set initial volume
channel part may be omitted, in which case the default channel is
used. This option may be given multiple times.
[/help]
+ [option initial-mood]
+ summary = mood or playlist to start with
+ arg_info = required_arg
+ arg_type = string
+ typestr = mood_spec
+ [help]
+ This mood or playlist is selected right after setting the initial
+ volume and before fade-out starts. If unset, fade-out starts
+ immediately.
+ [/help]
+ [option initial-delay]
+ summary = time before fade-out starts.
+ arg_info = required_arg
+ arg_type = uint32
+ typestr = seconds
+ default_val = 0
+ [help]
+ If left at the default, no initial delay occurs even if an initial
+ mood is given.
+ [/help]
[option fo-mood]
summary = mood or playlist for fade-out
arg_info = required_arg
}
EXPORT_CMD(fade);
-static void client_cmd(const char *cmd)
+static void run(const char *exe, const char *cmd)
{
int ret, status, fds[3] = {0, 0, 0};
pid_t pid;
- char *cmdline = make_message(BINDIR "/para_client %s", cmd);
+ char *cmdline = make_message("%s %s", exe, cmd);
PARA_NOTICE_LOG("%s\n", cmdline);
ret = para_exec_cmdline_pid(&pid, cmdline, fds);
exit(EXIT_FAILURE);
}
+static void client_cmd(const char *cmd)
+{
+ run(BINDIR "/para_client", cmd);
+}
+
+static void audioc_cmd(const char *cmd)
+{
+ run(BINDIR "/para_audioc", cmd);
+}
+
static void change_afs_mode(const char *afs_mode)
{
char *cmd;
return 1;
}
+static void stop(const struct mixer *m, struct mixer_handle *h)
+{
+ int ret, old_vol = 0;
+
+ ret = m->get(h);
+ if (ret > 0)
+ old_vol = ret;
+ fade(m, h, 0, 3);
+ audioc_cmd("off");
+ client_cmd("stop");
+ audioc_cmd("on");
+ m->set(h, old_vol);
+}
+
static int com_sleep(const struct mixer *m)
{
time_t t1, wake_time_epoch;
struct tm *tm;
int ret;
const char *wake_time = OPT_STRING_VAL(SLEEP, WAKE_TIME);
+ const char *initial_mood = OPT_STRING_VAL(SLEEP, INITIAL_MOOD);
const char *fo_mood = OPT_STRING_VAL(SLEEP, FO_MOOD);
const char *fi_mood = OPT_STRING_VAL(SLEEP, FI_MOOD);
const char *sleep_mood = OPT_STRING_VAL(SLEEP, SLEEP_MOOD);
}
wake_time_epoch = mktime(tm);
PARA_INFO_LOG("waketime: %d:%02d\n", tm->tm_hour, tm->tm_min);
- client_cmd("stop");
- sleep(1);
+ stop(m, h);
+ ret = set_initial_volume(m, h);
+ if (ret < 0)
+ goto close_mixer;
+ /*
+ * Setting the volume invalidates the current channel setting, so we
+ * have to set it again.
+ */
+ ret = set_channel(m, h, OPT_STRING_VAL(PARA_MIXER, MIXER_CHANNEL));
+ if (ret < 0)
+ goto close_mixer;
+ delay = OPT_UINT32_VAL(SLEEP, INITIAL_DELAY);
+ if (delay > 0 && initial_mood && *initial_mood) {
+ change_afs_mode(initial_mood);
+ client_cmd("play");
+ sleep(delay);
+ stop(m, h);
+ }
if (fot && fo_mood && *fo_mood) {
- ret = set_initial_volume(m, h);
- if (ret < 0)
- goto close_mixer;
change_afs_mode(fo_mood);
client_cmd("play");
- ret = set_channel(m, h, OPT_STRING_VAL(PARA_MIXER, MIXER_CHANNEL));
- if (ret < 0)
- goto close_mixer;
ret = fade(m, h, fov, fot);
if (ret < 0)
goto close_mixer;
if (!fot || !fo_mood) /* currently stopped */
client_cmd("play");
} else if (fot && fo_mood && *fo_mood) /* currently playing */
- client_cmd("stop");
+ stop(m, h);
m->close(&h);
if (!fit || !fi_mood || !*fi_mood) /* nothing to do */
return 1;
sleep(delay);
}
change_afs_mode(fi_mood);
- if (sleep_mood && *sleep_mood) /* currently playing */
- client_cmd("next");
- else /* currently stopped */
- client_cmd("play");
ret = open_mixer_and_set_channel(m, &h);
if (ret < 0)
return ret;
+ if (sleep_mood && *sleep_mood) /* currently playing */
+ stop(m, h);
+ client_cmd("play");
ret = fade(m, h, fiv, fit);
close_mixer:
m->close(&h);
#include "afh.h"
#include "afs.h"
#include "list.h"
-#include "mood.h"
/*
* Mood parser API. It's overkill to have an own header file for
/** Number of admissible files */
unsigned num;
};
-static struct afs_statistics statistics = {.normalization_divisor = 1};
-struct mood {
- /** The name of this mood. */
+/**
+ * Stores an instance of a loaded mood (parser and statistics).
+ *
+ * A structure of this type is allocated and initialized when a mood is loaded.
+ */
+struct mood_instance {
+ /** NULL means that this is the "dummy" mood. */
char *name;
- /** Info for the bison parser. */
+ /** Bison's abstract syntax tree, used to determine admissibility. */
struct mp_context *parser_context;
+ /** To compute the score. */
+ struct afs_statistics stats;
};
/*
- * If current_mood is NULL then no mood is currently open. If
- * current_mood->name is NULL, the dummy mood is currently open.
+ * If current_mood is NULL then no mood is currently loaded. If
+ * current_mood->name is NULL, the current mood is the dummy mood.
+ *
+ * The statistics are adjusted dynamically through this pointer as files are
+ * added, removed or played.
*/
-static struct mood *current_mood;
+static struct mood_instance *current_mood;
/*
* Find the position of the most-significant set bit.
return res;
}
-/* returns 1 if row admissible, 0 if not, negative on errors */
-static int row_is_admissible(const struct osl_row *aft_row, struct mood *m)
-{
- if (!m)
- return -E_NO_MOOD;
- return mp_eval_row(aft_row, m->parser_context);
-}
-
-static void destroy_mood(struct mood *m)
+static void destroy_mood(struct mood_instance *m)
{
if (!m)
return;
free(m);
}
-static struct mood *alloc_new_mood(const char *name)
+static struct mood_instance *alloc_new_mood(const char *name)
{
- struct mood *m = zalloc(sizeof(struct mood));
+ struct mood_instance *m = zalloc(sizeof(*m));
+
if (name)
m->name = para_strdup(name);
+ m->stats.normalization_divisor = 1;
return m;
}
-static int load_mood(const struct osl_row *mood_row, struct mood **m,
- char **errmsg)
+static int init_mood_parser(const char *mood_name, struct mood_instance **m,
+ char **err)
{
- char *mood_name;
struct osl_object mood_def;
int ret;
- ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def);
+ if (!*mood_name) {
+ if (err)
+ *err = make_message("empty mood name\n");
+ return -ERRNO_TO_PARA_ERROR(EINVAL);
+ }
+ ret = mood_get_def_by_name(mood_name, &mood_def);
if (ret < 0) {
- if (errmsg)
- *errmsg = make_message(
- "could not read mood definition");
+ if (err)
+ *err = make_message("could not read mood definition\n");
return ret;
}
- assert(*mood_name);
*m = alloc_new_mood(mood_name);
- PARA_INFO_LOG("opening mood %s\n", mood_name);
- ret = mp_init(mood_def.data, mood_def.size, &(*m)->parser_context, errmsg);
+ PARA_INFO_LOG("loading mood %s\n", mood_name);
+ ret = mp_init(mood_def.data, mood_def.size, &(*m)->parser_context, err);
osl_close_disk_object(&mood_def);
if (ret < 0)
destroy_mood(*m);
static int check_mood(struct osl_row *mood_row, void *data)
{
- struct para_buffer *pb = data;
+ struct afs_callback_arg *aca = data;
char *mood_name, *errmsg;
struct osl_object mood_def;
- struct mood *m;
+ struct mood_instance *m;
int ret = mood_get_name_and_def_by_row(mood_row, &mood_name, &mood_def);
if (ret < 0) {
- para_printf(pb, "cannot read mood\n");
+ afs_error(aca, "cannot read mood\n");
return ret;
}
if (!*mood_name) /* ignore dummy row */
ret = mp_init(mood_def.data, mood_def.size, &m->parser_context,
&errmsg);
if (ret < 0) {
- para_printf(pb, "%s: %s\n", mood_name, errmsg);
+ afs_error(aca, "%s: %s\n%s\n", mood_name, errmsg,
+ para_strerror(-ret));
free(errmsg);
- para_printf(pb, "%s\n", para_strerror(-ret));
} else
destroy_mood(m);
ret = 1; /* don't fail the loop on invalid mood definitions */
/**
* Check all moods for syntax errors.
*
- * \param aca Only ->pbout is used for diagnostics.
+ * \param aca Output goes to ->pbout, errors to ->fd on the error band.
*
* \return Negative on fatal errors. Inconsistent mood definitions are not
* considered an error.
int mood_check_callback(struct afs_callback_arg *aca)
{
para_printf(&aca->pbout, "checking moods...\n");
- return osl(osl_rbtree_loop(moods_table, BLOBCOL_ID, &aca->pbout,
- check_mood));
+ return osl(osl_rbtree_loop(moods_table, BLOBCOL_ID, aca, check_mood));
}
/*
* overflows and rounding errors we store the common divisor of the
* correction factors separately.
*/
-static long compute_score(struct afs_info *afsi)
+static long compute_score(struct afs_info *afsi,
+ const struct afs_statistics *stats)
{
int64_t mean_n, mean_l,score_n, score_l;
- assert(statistics.normalization_divisor > 0);
- assert(statistics.num > 0);
- mean_n = statistics.num_played_sum / statistics.num;
- mean_l = statistics.last_played_sum / statistics.num;
+ assert(stats->normalization_divisor > 0);
+ assert(stats->num > 0);
+ mean_n = stats->num_played_sum / stats->num;
+ mean_l = stats->last_played_sum / stats->num;
score_n = -((int64_t)afsi->num_played - mean_n)
- * statistics.num_played_correction
- / statistics.normalization_divisor;
+ * stats->num_played_correction
+ / stats->normalization_divisor;
score_l = -((int64_t)afsi->last_played - mean_l)
- * statistics.last_played_correction
- / statistics.normalization_divisor;
+ * stats->last_played_correction
+ / stats->normalization_divisor;
return (score_n + score_l) / 2;
}
-static int add_afs_statistics(const struct osl_row *row)
+static int add_afs_statistics(const struct osl_row *row,
+ struct afs_statistics *stats)
{
uint64_t n, x, s, q;
struct afs_info afsi;
ret = get_afsi_of_row(row, &afsi);
if (ret < 0)
return ret;
- n = statistics.num;
+ n = stats->num;
x = afsi.last_played;
- s = statistics.last_played_sum;
+ s = stats->last_played_sum;
if (n > 0) {
q = (x > s / n)? x - s / n : s / n - x;
- statistics.last_played_qd += q * q * n / (n + 1);
+ stats->last_played_qd += q * q * n / (n + 1);
}
- statistics.last_played_sum += x;
+ stats->last_played_sum += x;
x = afsi.num_played;
- s = statistics.num_played_sum;
+ s = stats->num_played_sum;
if (n > 0) {
q = (x > s / n)? x - s / n : s / n - x;
- statistics.num_played_qd += q * q * n / (n + 1);
+ stats->num_played_qd += q * q * n / (n + 1);
}
- statistics.num_played_sum += x;
- statistics.num++;
+ stats->num_played_sum += x;
+ stats->num++;
return 1;
}
static int del_afs_statistics(const struct osl_row *row)
{
+ struct afs_statistics *stats = ¤t_mood->stats;
uint64_t n, s, q, a, new_s;
struct afs_info afsi;
int ret;
ret = get_afsi_of_row(row, &afsi);
if (ret < 0)
return ret;
- n = statistics.num;
+ n = stats->num;
assert(n);
if (n == 1) {
- memset(&statistics, 0, sizeof(statistics));
- statistics.normalization_divisor = 1;
+ memset(stats, 0, sizeof(*stats));
+ stats->normalization_divisor = 1;
return 1;
}
- s = statistics.last_played_sum;
- q = statistics.last_played_qd;
+ s = stats->last_played_sum;
+ q = stats->last_played_qd;
a = afsi.last_played;
new_s = s - a;
- statistics.last_played_sum = new_s;
- statistics.last_played_qd = q + s * s / n - a * a
+ stats->last_played_sum = new_s;
+ stats->last_played_qd = q + s * s / n - a * a
- new_s * new_s / (n - 1);
- s = statistics.num_played_sum;
- q = statistics.num_played_qd;
+ s = stats->num_played_sum;
+ q = stats->num_played_qd;
a = afsi.num_played;
new_s = s - a;
- statistics.num_played_sum = new_s;
- statistics.num_played_qd = q + s * s / n - a * a
+ stats->num_played_sum = new_s;
+ stats->num_played_qd = q + s * s / n - a * a
- new_s * new_s / (n - 1);
- statistics.num--;
+ stats->num--;
return 1;
}
/*
- * At mood open time we determine the set of admissible files for the given
+ * At mood load time we determine the set of admissible files for the given
* mood where each file is identified by a pointer to a row of the audio file
* table. In the first pass the pointers are added to a temporary array and
* statistics are computed. When all admissible files have been processed in
*/
struct admissible_array {
/** Files are admissible wrt. this mood. */
- struct mood *m;
+ struct mood_instance *m;
/** The size of the array */
unsigned size;
/** Pointer to the array of admissible files. */
static int add_if_admissible(struct osl_row *aft_row, void *data)
{
struct admissible_array *aa = data;
- int ret;
+ struct afs_statistics *stats = &aa->m->stats;
- ret = row_is_admissible(aft_row, aa->m);
- if (ret <= 0)
- return ret;
- if (statistics.num >= aa->size) {
+ if (!mp_eval_row(aft_row, aa->m->parser_context))
+ return 0;
+ if (stats->num >= aa->size) {
aa->size *= 2;
aa->size += 100;
aa->array = arr_realloc(aa->array, aa->size,
sizeof(struct osl_row *));
}
- aa->array[statistics.num] = aft_row;
- return add_afs_statistics(aft_row);
+ aa->array[stats->num] = aft_row;
+ return add_afs_statistics(aft_row, stats);
}
/**
return old_qd + delta * (sigma - 2 * old_sum / n - delta / n);
}
-static int update_afs_statistics(struct afs_info *old_afsi,
+static void update_afs_statistics(struct afs_info *old_afsi,
struct afs_info *new_afsi)
{
- unsigned n;
- int ret = get_num_admissible_files(&n);
-
- if (ret < 0)
- return ret;
- assert(n);
-
- statistics.last_played_qd = update_quadratic_deviation(n,
- statistics.last_played_qd, old_afsi->last_played,
- new_afsi->last_played, statistics.last_played_sum);
- statistics.last_played_sum += new_afsi->last_played - old_afsi->last_played;
-
- statistics.num_played_qd = update_quadratic_deviation(n,
- statistics.num_played_qd, old_afsi->num_played,
- new_afsi->num_played, statistics.num_played_sum);
- statistics.num_played_sum += new_afsi->num_played - old_afsi->num_played;
- return 1;
+ struct afs_statistics *stats = ¤t_mood->stats;
+
+ assert(stats->num > 0);
+ stats->last_played_qd = update_quadratic_deviation(stats->num,
+ stats->last_played_qd, old_afsi->last_played,
+ new_afsi->last_played, stats->last_played_sum);
+ stats->last_played_sum += new_afsi->last_played - old_afsi->last_played;
+
+ stats->num_played_qd = update_quadratic_deviation(stats->num,
+ stats->num_played_qd, old_afsi->num_played,
+ new_afsi->num_played, stats->num_played_sum);
+ stats->num_played_sum += new_afsi->num_played - old_afsi->num_played;
}
-static int add_to_score_table(const struct osl_row *aft_row)
+static int add_to_score_table(const struct osl_row *aft_row,
+ const struct afs_statistics *stats)
{
long score;
struct afs_info afsi;
if (ret < 0)
return ret;
- score = compute_score(&afsi);
+ score = compute_score(&afsi, stats);
return score_add(aft_row, score);
}
}
/**
- * Delete one entry from the statistics and from the score table.
+ * Delete an audio file from the score table and update mood statistics.
*
- * \param aft_row The audio file which is no longer admissible.
+ * \param aft_row Identifies the row to delete.
*
- * \return Positive on success, negative on errors.
+ * \return Standard.
*
* \sa \ref score_delete().
*/
static int mood_delete_audio_file(const struct osl_row *aft_row)
{
- int ret;
-
- ret = row_belongs_to_score_table(aft_row, NULL);
- if (ret < 0)
- return ret;
- if (!ret) /* not admissible, nothing to do */
- return 1;
+ if (!row_belongs_to_score_table(aft_row))
+ return 0;
return delete_from_statistics_and_score_table(aft_row);
}
struct afs_info *old_afsi)
{
long score, percent;
- int ret, is_admissible, was_admissible = 0;
+ int ret;
+ bool is_admissible, was_admissible;
struct afs_info afsi;
- unsigned rank;
if (!current_mood)
return 1; /* nothing to do */
- ret = row_belongs_to_score_table(aft_row, &rank);
- if (ret < 0)
- return ret;
- was_admissible = ret;
- ret = row_is_admissible(aft_row, current_mood);
- if (ret < 0)
- return ret;
- is_admissible = (ret > 0);
+ was_admissible = row_belongs_to_score_table(aft_row);
+ is_admissible = mp_eval_row(aft_row, current_mood->parser_context);
if (!was_admissible && !is_admissible)
return 1;
if (was_admissible && !is_admissible)
return delete_from_statistics_and_score_table(aft_row);
if (!was_admissible && is_admissible) {
- ret = add_afs_statistics(aft_row);
+ ret = add_afs_statistics(aft_row, ¤t_mood->stats);
if (ret < 0)
return ret;
- return add_to_score_table(aft_row);
+ return add_to_score_table(aft_row, ¤t_mood->stats);
}
/* update score */
ret = get_afsi_of_row(aft_row, &afsi);
if (ret < 0)
return ret;
- if (old_afsi) {
- ret = update_afs_statistics(old_afsi, &afsi);
- if (ret < 0)
- return ret;
- }
- score = compute_score(&afsi);
+ if (old_afsi)
+ update_afs_statistics(old_afsi, &afsi);
+ score = compute_score(&afsi, ¤t_mood->stats);
PARA_DEBUG_LOG("score: %li\n", score);
percent = (score + 100) / 3;
if (percent > 100)
percent = 100;
else if (percent < 0)
percent = 0;
- PARA_DEBUG_LOG("moving from rank %u to %li%%\n", rank, percent);
+ PARA_DEBUG_LOG("moving to %li%%\n", percent);
return score_update(aft_row, percent);
}
/* sse: seconds since epoch. */
-static void log_statistics(int64_t sse)
+static char *get_statistics(struct mood_instance *m, int64_t sse)
{
- unsigned n = statistics.num;
+ unsigned n = m->stats.num;
int mean_days, sigma_days;
- assert(current_mood);
- PARA_NOTICE_LOG("loaded mood %s\n", current_mood->name?
- current_mood->name : "(dummy)");
- if (!n) {
- PARA_WARNING_LOG("no admissible files\n");
- return;
- }
- PARA_NOTICE_LOG("%u admissible files\n", statistics.num);
- mean_days = (sse - statistics.last_played_sum / n) / 3600 / 24;
- sigma_days = int_sqrt(statistics.last_played_qd / n) / 3600 / 24;
- PARA_NOTICE_LOG("last_played mean/sigma: %d/%d days\n", mean_days, sigma_days);
- PARA_NOTICE_LOG("num_played mean/sigma: %" PRId64 "/%" PRIu64 "\n",
- statistics.num_played_sum / n,
- int_sqrt(statistics.num_played_qd / n));
- PARA_NOTICE_LOG("num_played correction factor: %" PRId64 "\n",
- statistics.num_played_correction);
- PARA_NOTICE_LOG("last_played correction factor: %" PRId64 "\n",
- statistics.last_played_correction);
- PARA_NOTICE_LOG("normalization divisor: %" PRId64 "\n",
- statistics.normalization_divisor);
+ mean_days = (sse - m->stats.last_played_sum / n) / 3600 / 24;
+ sigma_days = int_sqrt(m->stats.last_played_qd / n) / 3600 / 24;
+ return make_message(
+ "loaded mood %s (%u files)\n"
+ "last_played mean/sigma: %d/%d days\n"
+ "num_played mean/sigma: %" PRId64 "/%" PRIu64 "\n"
+ ,
+ m->name? m->name : "(dummy)",
+ n,
+ mean_days, sigma_days,
+ m->stats.num_played_sum / n,
+ int_sqrt(m->stats.num_played_qd / n)
+ );
}
-/**
- * Close the current mood.
- *
- * Frees all resources of the current mood.
- */
-void close_current_mood(void)
+/** Free all resources of the current mood, if any. */
+void mood_unload(void)
{
destroy_mood(current_mood);
current_mood = NULL;
- memset(&statistics, 0, sizeof(statistics));
- statistics.normalization_divisor = 1;
}
-static void compute_correction_factors(int64_t sse)
+static void compute_correction_factors(int64_t sse, struct afs_statistics *s)
{
- struct afs_statistics *s = &statistics;
-
if (s->num > 0) {
s->normalization_divisor = int_sqrt(s->last_played_qd)
* int_sqrt(s->num_played_qd) / s->num / 100;
/**
* Change the current mood.
*
- * \param mood_name The name of the mood to open.
- * \param errmsg Error description is returned here.
+ * \param mood_name The name of the mood to load.
+ * \param msg Error message or mood info is returned here.
*
* If \a mood_name is \a NULL, load the dummy mood that accepts every audio file
* and uses a scoring method based only on the \a last_played information.
*
- * The errmsg pointer may be NULL, in which case no error message will be
- * returned. If a non-NULL pointer is given, the caller must free *errmsg.
- *
- * If there is already an open mood, it will be closed first.
+ * If the message pointer is not NULL, a suitable message is returned there in
+ * all cases. The caller must free this string.
*
- * \return Positive on success, negative on errors.
+ * \return The number of admissible files on success, negative on errors. It is
+ * not considered an error if no files are admissible.
*
* \sa struct \ref afs_info::last_played, \ref mp_eval_row().
*/
-int change_current_mood(const char *mood_name, char **errmsg)
+int mood_load(const char *mood_name, char **msg)
{
int i, ret;
- struct admissible_array aa = {
- .size = 0,
- .array = NULL
- };
+ struct admissible_array aa = {.size = 0};
/*
* We can not use the "now" pointer from sched.c here because we are
* called before schedule(), which initializes "now".
struct timeval rnow;
if (mood_name) {
- struct mood *m;
- struct osl_row *row;
- struct osl_object obj;
-
- if (!*mood_name) {
- *errmsg = make_message("empty mood name");
- return -ERRNO_TO_PARA_ERROR(EINVAL);
- }
- obj.data = (char *)mood_name;
- obj.size = strlen(mood_name) + 1;
- ret = osl(osl_get_row(moods_table, BLOBCOL_NAME, &obj, &row));
- if (ret < 0) {
- if (errmsg)
- *errmsg = make_message("no such mood: %s",
- mood_name);
- return ret;
- }
- ret = load_mood(row, &m, errmsg);
+ ret = init_mood_parser(mood_name, &aa.m, msg);
if (ret < 0)
return ret;
- close_current_mood();
- current_mood = m;
- } else { /* load dummy mood */
- close_current_mood();
- current_mood = alloc_new_mood(NULL);
- }
- aa.m = current_mood;
+ } else /* load dummy mood */
+ aa.m = alloc_new_mood(NULL);
PARA_NOTICE_LOG("computing statistics of admissible files\n");
ret = audio_file_loop(&aa, add_if_admissible);
if (ret < 0) {
- if (errmsg)
- *errmsg = make_message("audio file loop failed");
+ if (msg) /* false if we are called via the event handler */
+ *msg = make_message("audio file loop failed\n");
goto out;
}
clock_get_realtime(&rnow);
- compute_correction_factors(rnow.tv_sec);
- log_statistics(rnow.tv_sec);
- for (i = 0; i < statistics.num; i++) {
- ret = add_to_score_table(aa.array[i]);
+ compute_correction_factors(rnow.tv_sec, &aa.m->stats);
+ if (aa.m->stats.num == 0) {
+ if (msg)
+ *msg = make_message("no admissible files\n");
+ ret = 0;
+ goto out;
+ }
+ for (i = 0; i < aa.m->stats.num; i++) {
+ ret = add_to_score_table(aa.array[i], &aa.m->stats);
if (ret < 0) {
- if (errmsg)
- *errmsg = make_message(
- "could not add row to score table");
+ if (msg)
+ *msg = make_message(
+ "could not add row to score table\n");
goto out;
}
}
- ret = statistics.num;
+ /* success */
+ if (msg)
+ *msg = get_statistics(aa.m, rnow.tv_sec);
+ ret = aa.m->stats.num;
+ mood_unload();
+ current_mood = aa.m;
out:
free(aa.array);
if (ret < 0)
- close_current_mood();
+ destroy_mood(aa.m);
return ret;
}
/*
- * Close and re-open the current mood.
+ * Empty the score table and start over.
*
* This function is called on events which render the current list of
* admissible files useless, for example if an attribute is removed from the
* attribute table.
- *
- * If no mood is currently open, the function returns success.
*/
static int reload_current_mood(void)
{
int ret;
char *mood_name = NULL;
- ret = clear_score_table();
- if (ret < 0)
- return ret;
- if (!current_mood)
- return 1;
+ assert(current_mood);
+ score_clear();
PARA_NOTICE_LOG("reloading %s\n", current_mood->name?
current_mood->name : "(dummy)");
if (current_mood->name)
mood_name = para_strdup(current_mood->name);
- close_current_mood();
- ret = change_current_mood(mood_name, NULL);
+ mood_unload();
+ ret = mood_load(mood_name, NULL);
free(mood_name);
return ret;
}
+++ /dev/null
-/* Copyright (C) 2007 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
-
-/** \file mood.h Public functions of mood.c. */
-
-int change_current_mood(const char *mood_name, char **errmsg);
-void close_current_mood(void);
-int mood_check_callback(struct afs_callback_arg *aca);
* function returns true (without looking at the audio file metadata) to
* indicate that the given audio file should be considered admissible.
*
- * \sa \ref change_current_mood(), \ref mp_eval_ast().
+ * \sa \ref mood_load(), \ref mp_eval_ast().
*/
bool mp_eval_row(const struct osl_row *aft_row, struct mp_context *ctx)
{
#include "list.h"
#include "fd.h"
+/* Whether the given address conforms to the IPv4 address format. */
+static inline bool is_valid_ipv4_address(const char *address)
+{
+ struct in_addr test_it;
+ return inet_pton(AF_INET, address, &test_it) != 0;
+}
+
/**
* Parse and validate IPv4 address/netmask string.
*
return NULL;
}
-
-/**
- * Match string as a candidate IPv4 address.
- *
- * \param address The string to match.
- * \return True if \a address has "dot-quad" format.
- */
static bool is_v4_dot_quad(const char *address)
{
bool result;
return result;
}
+/* Whether a string conforms to IPv6 address format (RFC 4291). */
+static inline bool is_valid_ipv6_address(const char *address)
+{
+ struct in6_addr test_it;
+ return inet_pton(AF_INET6, address, &test_it) != 0;
+}
+
/**
* Perform basic syntax checking on the host-part of an URL:
*
* \param transport Transport protocol name (e.g. "udp", "tcp"), or NULL.
* \return Pointer to static result buffer.
*
- * \sa getservent(3), services(5), nsswitch.conf(5).
+ * \sa getservbyport(3), services(5), nsswitch.conf(5).
*/
const char *stringify_port(int port, const char *transport)
{
return service;
}
-/**
- * Determine the socket type for a given layer-4 protocol.
- *
- * \param l4type The symbolic name of the transport-layer protocol.
- *
- * \sa ip(7), socket(2).
+#ifndef SOCK_DCCP
+#define SOCK_DCCP 6 /**< Linux socket type. */
+#endif
+
+/*
+ * Determine the socket type, given the symbolic name of the transport-layer
+ * protocol. See ip(7), socket(2).
*/
static inline int sock_type(const unsigned l4type)
{
return -1; /* not supported here */
}
-/**
- * Pretty-print transport-layer name.
- */
+/* Pretty-print transport-layer name. */
static const char *layer4_name(const unsigned l4type)
{
switch (l4type) {
struct list_head node; /**< FIFO, as sockopt order matters. */
};
-/** FIFO list of pre-connection socket options to be set */
+/**
+ * List of pre-connection socket options to be set.
+ *
+ * This list contains transport-layer independent encapsulation of socket
+ * options that need to be registered prior to setting up a connection.
+ */
struct flowopts {
struct list_head sockopts;
};
list_add_tail(&new->node, &fo->sockopts);
}
-/** Set the entire bunch of pre-connection options at once. */
+/* Set the entire bunch of pre-connection options at once. */
static void flowopt_setopts(int sockfd, struct flowopts *fo)
{
struct pre_conn_opt *pc;
if (ai)
freeaddrinfo(ai);
if (ret < 0) {
- PARA_ERROR_LOG("can not create %s socket %s#%d.\n",
+ PARA_NOTICE_LOG("can not create %s socket %s#%d.\n",
layer4_name(l4type), host? host : (passive?
"[loopback]" : "[localhost]"), port_number);
}
return para_listen(l4type, NULL, port);
}
-/**
- * Determine IPv4/v6 socket address length.
- * \param sa Container of IPv4 or IPv6 address.
- * \return Address-family dependent address length.
- */
+/* Compute the address-family dependent address length of an IPv4/v6 socket. */
static socklen_t salen(const struct sockaddr *sa)
{
assert(sa->sa_family == AF_INET || sa->sa_family == AF_INET6);
: sizeof(struct sockaddr_in);
}
-/** True if @ss holds a v6-mapped-v4 address (RFC 4291, 2.5.5.2) */
+/* True if ss holds a v6-mapped-v4 address (RFC 4291, 2.5.5.2) */
static bool SS_IS_ADDR_V4MAPPED(const struct sockaddr_storage *ss)
{
const struct sockaddr_in6 *ia6 = (const struct sockaddr_in6 *)ss;
return ss->ss_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&ia6->sin6_addr);
}
-/**
+/*
* Process IPv4/v6 address, turn v6-mapped-v4 address into normal IPv4 address.
- * \param ss Container of IPv4/6 address.
- * \return Pointer to normalized address (may be static storage).
+ * ss: Container of IPv4/6 address.
+ * Returns: Pointer to normalized address (may be static storage).
*
* \sa RFC 3493.
*/
return (const struct sockaddr *)ss;
}
-/**
+/*
* Generic/fallback MTU values
*
* These are taken from RFC 1122, RFC 2460, and RFC 5405.
return af_type == AF_INET6 ? 1280 : 576;
}
-/** Crude approximation of IP header overhead - neglecting options. */
+/* Crude approximation of IP header overhead - neglecting options. */
static inline int estimated_header_overhead(const int af_type)
{
return af_type == AF_INET6 ? 40 : 20;
return -ERRNO_TO_PARA_ERROR(errno);
}
+#ifndef DCCP_SOCKOPT_AVAILABLE_CCIDS
+#define DCCP_SOCKOPT_AVAILABLE_CCIDS 12 /**< List of supported CCIDs. */
+#endif
+
/**
* Probe the list of DCCP CCIDs configured on this host.
* \param ccid_array Pointer to return statically allocated array in.
socklen_t nccids = sizeof(ccids);
int ret, fd;
- ret = fd = makesock(IPPROTO_DCCP, 1, NULL, 0, NULL);
+ ret = fd = makesock(IPPROTO_DCCP, true /* passive */, NULL, 0, NULL);
if (ret < 0)
return ret;
return nccids;
}
+/**
+ * The buffer size of the sun_path component of struct sockaddr_un.
+ *
+ * While glibc doesn't define UNIX_PATH_MAX, it documents it has being limited
+ * to 108 bytes. On NetBSD it is only 104 bytes though. We trust UNIX_PATH_MAX
+ * if it is defined and use the size of the ->sun_path member otherwise. This
+ * should be safe everywhere.
+ */
+#ifndef UNIX_PATH_MAX
+#define UNIX_PATH_MAX (sizeof(((struct sockaddr_un *)0)->sun_path))
+#endif
+
/*
* Prepare a structure for AF_UNIX socket addresses.
*
/* Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
/** \file net.h exported symbols from net.c */
-/**
- * The buffer size of the sun_path component of struct sockaddr_un.
- *
- * While glibc doesn't define \p UNIX_PATH_MAX, it documents it has being
- * limited to 108 bytes. On NetBSD it is only 104 bytes though. We trust \p
- * UNIX_PATH_MAX if it is defined and use the size of the ->sun_path member
- * otherwise. This should be safe everywhere.
- */
-#ifndef UNIX_PATH_MAX
-#define UNIX_PATH_MAX (sizeof(((struct sockaddr_un *)0)->sun_path))
-#endif
-
/* Userland defines for Linux DCCP support. */
-#ifndef IPPROTO_DCCP
-#define IPPROTO_DCCP 33 /**< IANA assigned value. */
-#endif
-
-#ifndef SOCK_DCCP
-#define SOCK_DCCP 6 /**< Linux socket type. */
-#endif
-
-#ifndef DCCP_SOCKOPT_RX_CCID
-/** Per-connection CCID support (set/get the RX CCID, since v2.6.30-rc1). */
-#define DCCP_SOCKOPT_RX_CCID 15
-#endif
-
#ifndef SOL_DCCP
#define SOL_DCCP 269 /**< Linux socket level. */
#endif
-#ifndef DCCP_SOCKOPT_GET_CUR_MPS
-#define DCCP_SOCKOPT_GET_CUR_MPS 5 /**< Max packet size, RFC 4340, 14. */
-#endif
-
-#ifndef DCCP_SOCKOPT_AVAILABLE_CCIDS
-#define DCCP_SOCKOPT_AVAILABLE_CCIDS 12 /**< List of supported CCIDs. */
-#endif
-
-#ifndef DCCP_SOCKOPT_CCID
-#define DCCP_SOCKOPT_CCID 13 /**< Sets both TX/RX CCID. */
-#endif
-
-#ifndef DCCP_SOCKOPT_TX_CCID
-#define DCCP_SOCKOPT_TX_CCID 14 /**< Set/get the TX CCID. */
-#endif
-
/** The maximum length of the host component in an URL. */
#define MAX_HOSTLEN 256
-/**
- * Flowopts: Transport-layer independent encapsulation of socket options
- * that need to be registered prior to setting up a connection.
- */
+/* Opaque, only known to net.c. */
struct flowopts;
-extern struct flowopts *flowopt_new(void);
-extern void flowopt_add(struct flowopts *fo, int level, int opt,
+struct flowopts *flowopt_new(void);
+void flowopt_add(struct flowopts *fo, int level, int opt,
const char *name, const void *val, int len);
void flowopt_cleanup(struct flowopts *fo);
-/** Flowopt shortcut macros */
-#define OPT_ADD(fo, lev, opt, val, len) flowopt_add(fo, lev, opt, #opt, val, len)
/**
* Functions to parse and validate (parts of) URLs.
*/
-extern char *parse_cidr(const char *cidr,
- char *addr, ssize_t addrlen, int32_t *netmask);
-extern char *parse_url(const char *url,
- char *host, ssize_t hostlen, int32_t *port);
+char *parse_cidr(const char *cidr,
+ char *addr, ssize_t addrlen, int32_t *netmask);
+char *parse_url(const char *url,
+ char *host, ssize_t hostlen, int32_t *port);
char *format_url(const char *url, int default_port);
-extern const char *stringify_port(int port, const char *transport);
-/**
- * Ensure that string conforms to the IPv4 address format.
- *
- * \param address The address string to check.
- *
- * \return 1 if \a address conforms to the IPv4 address format, else 0.
- */
-_static_inline_ bool is_valid_ipv4_address(const char *address)
-{
- struct in_addr test_it;
-
- return inet_pton(AF_INET, address, &test_it) != 0;
-}
-
-/**
- * Ensure that string conforms to IPv6 address format.
- *
- * \param address The address string to check.
- *
- * \return 1 if string has a valid IPv6 address syntax, 0 if not.
- * \sa RFC 4291.
- */
-_static_inline_ bool is_valid_ipv6_address(const char *address)
-{
- struct in6_addr test_it;
-
- return inet_pton(AF_INET6, address, &test_it) != 0;
-}
+const char *stringify_port(int port, const char *transport);
int lookup_address(unsigned l4type, bool passive, const char *host,
int port_number, struct addrinfo **result);
int makesock_addrinfo(unsigned l4type, bool passive, struct addrinfo *ai,
struct flowopts *fo);
-static inline int para_connect_simple(unsigned l4type,
- const char *host, uint16_t port)
+static inline int para_connect(unsigned l4type, const char *host, uint16_t port)
{
- return makesock(l4type, 0, host, port, NULL);
+ return makesock(l4type, false, host, port, NULL);
}
void extract_v4_addr(const struct sockaddr_storage *ss, struct in_addr *ia);
int para_listen_simple(unsigned l4type, uint16_t port);
/** Pretty-printing of IPv4/6 socket addresses */
-extern char *remote_name(int sockfd);
+char *remote_name(int sockfd);
/**
* Determining maximum payload (packet) size
*/
-extern int generic_max_transport_msg_size(int sockfd);
+int generic_max_transport_msg_size(int sockfd);
int recv_bin_buffer(int fd, char *buf, size_t size);
int recv_buffer(int fd, char *buf, size_t size);
/**
* Functions and definitions to support \p IPPROTO_DCCP
*/
-/** Estimated worst-case length of a DCCP header including options. */
-#define DCCP_MAX_HEADER 128
/** Hardcoded maximum number of separate CCID modules compiled into a host. */
#define DCCP_MAX_HOST_CCIDS 20
-extern int dccp_available_ccids(uint8_t **ccid_array);
+int dccp_available_ccids(uint8_t **ccid_array);
#define PARA_CRIT_LOG(f,...) para_log(LL_CRIT, "%s: " f, __FUNCTION__, ## __VA_ARGS__)
#define PARA_EMERG_LOG(f,...) para_log(LL_EMERG, "%s: " f, __FUNCTION__, ## __VA_ARGS__)
+/** \cond status_items */
#define STATUS_ITEMS \
STATUS_ITEM(basename) \
STATUS_ITEM(status) \
enum status_items {STATUS_ITEMS NUM_STAT_ITEMS};
#undef STATUS_ITEM
#define STATUS_ITEM(_name) #_name,
+/** \endcond status items */
extern const char *status_item_list[];
/** Loop over each status item. */
/** \file playlist.c Functions for loading and saving playlists. */
/** Structure used for adding entries to a playlist. */
-struct playlist_info {
+struct playlist_instance {
/** The name of the playlist. */
char *name;
/** The number of entries currently in the playlist. */
unsigned length;
};
-static struct playlist_info current_playlist;
+static struct playlist_instance current_playlist;
/**
* Re-insert an audio file into the tree of admissible files.
static int add_playlist_entry(char *path, void *data)
{
- struct playlist_info *playlist = data;
+ struct playlist_instance *playlist = data;
struct osl_row *aft_row;
int ret = aft_get_row_of_path(path, &aft_row);
return 1;
}
-/* returns -E_PLAYLIST_LOADED on _success_ to terminate the loop */
-static int load_playlist(struct osl_row *row, void *data)
-{
- struct playlist_info *playlist = data;
- struct osl_object playlist_def;
- char *playlist_name;
- int ret;
-
- ret = pl_get_name_and_def_by_row(row, &playlist_name, &playlist_def);
- if (ret < 0)
- goto err;
- playlist->length = 0;
- ret = for_each_line(FELF_READ_ONLY, playlist_def.data,
- playlist_def.size, add_playlist_entry, playlist);
- osl_close_disk_object(&playlist_def);
- if (ret < 0)
- goto err;
- ret = -E_PLAYLIST_EMPTY;
- if (!playlist->length)
- goto err;
- playlist->name = para_strdup(playlist_name);
- PARA_NOTICE_LOG("loaded playlist %s (%u files)\n", playlist->name,
- playlist->length);
- return -E_PLAYLIST_LOADED;
-err:
- if (ret != -E_DUMMY_ROW)
- PARA_NOTICE_LOG("unable to load playlist (%s)\n",
- para_strerror(-ret));
- return 1;
-}
-
static int check_playlist_path(char *path, void *data)
{
- struct para_buffer *pb = data;
+ struct afs_callback_arg *aca = data;
struct osl_row *aft_row;
int ret = aft_get_row_of_path(path, &aft_row);
if (ret < 0)
- para_printf(pb, "%s: %s\n", path, para_strerror(-ret));
+ afs_error(aca, "%s: %s\n", path, para_strerror(-ret));
return 1; /* do not fail the loop on bad paths */
}
static int check_playlist(struct osl_row *row, void *data)
{
- struct para_buffer *pb = data;
+ struct afs_callback_arg *aca = data;
+ struct para_buffer *pb = &aca->pbout;
struct osl_object playlist_def;
char *playlist_name;
int ret = pl_get_name_and_def_by_row(row, &playlist_name, &playlist_def);
if (ret < 0) { /* log error, but continue */
- para_printf(pb, "failed to get playlist data: %s\n",
+ afs_error(aca, "failed to get playlist data: %s\n",
para_strerror(-ret));
return 1;
}
if (*playlist_name) { /* skip dummy row */
para_printf(pb, "checking playlist %s...\n", playlist_name);
for_each_line(FELF_READ_ONLY, playlist_def.data,
- playlist_def.size, check_playlist_path, pb);
+ playlist_def.size, check_playlist_path, aca);
}
osl_close_disk_object(&playlist_def);
return 1;
int playlist_check_callback(struct afs_callback_arg *aca)
{
para_printf(&aca->pbout, "checking playlists...\n");
- return osl(osl_rbtree_loop(playlists_table, BLOBCOL_ID, &aca->pbout,
+ return osl(osl_rbtree_loop(playlists_table, BLOBCOL_ID, aca,
check_playlist));
}
-/**
- * Close the current playlist.
- *
- * \sa \ref playlist_open().
- */
-void playlist_close(void)
+/** Free all resources of the current playlist, if any. */
+void playlist_unload(void)
{
if (!current_playlist.name)
return;
free(current_playlist.name);
current_playlist.name = NULL;
+ current_playlist.length = 0;
}
/**
- * Open the given playlist.
+ * Populate the score table from the paths of a playlist database object.
*
- * \param name The name of the playlist to open.
+ * This loads the blob object which corresponds to the given name from the
+ * playlist table. Each line of the blob is regarded as a path which is looked
+ * up in the audio file table. If the path lookup succeeds, a reference to the
+ * corresponding row of the audio file table is added to the score table.
*
- * Files which are listed in the playlist, but not contained in the database
- * are ignored. This is not considered an error.
+ * \param name The name of the playlist to load.
+ * \param msg Error message or playlist info is returned here.
*
- * \return Standard.
+ * \return The length of the loaded playlist on success, negative error code
+ * else. Files which are listed in the playlist, but are not contained in the
+ * database are ignored. This is not considered an error.
*/
-int playlist_open(const char *name)
+int playlist_load(const char *name, char **msg)
{
- struct osl_object obj;
int ret;
- struct osl_row *row;
+ struct playlist_instance *playlist = ¤t_playlist;
+ struct osl_object playlist_def;
- obj.data = (char *)name;
- obj.size = strlen(obj.data);
- ret = osl(osl_get_row(playlists_table, BLOBCOL_NAME, &obj, &row));
+ ret = pl_get_def_by_name(name, &playlist_def);
if (ret < 0) {
- PARA_NOTICE_LOG("failed to load playlist %s\n", name);
+ *msg = make_message("could not read playlist %s\n", name);
return ret;
}
- playlist_close();
- ret = load_playlist(row, ¤t_playlist);
- return (ret == -E_PLAYLIST_LOADED)? current_playlist.length : ret;
+ playlist_unload();
+ ret = for_each_line(FELF_READ_ONLY, playlist_def.data,
+ playlist_def.size, add_playlist_entry, playlist);
+ osl_close_disk_object(&playlist_def);
+ if (ret < 0)
+ goto err;
+ ret = -E_PLAYLIST_EMPTY;
+ if (!playlist->length)
+ goto err;
+ playlist->name = para_strdup(name);
+ *msg = make_message("loaded playlist %s (%u files)\n", playlist->name,
+ playlist->length);
+ /* success */
+ return current_playlist.length;
+err:
+ PARA_NOTICE_LOG("unable to load playlist %s\n", name);
+ *msg = make_message("unable to load playlist %s\n", name);
+ return ret;
}
static int search_path(char *path, void *data)
static int handle_audio_file_event(enum afs_events event, void *data)
{
- int ret, was_admissible = 0, is_admissible;
+ int ret;
+ bool was_admissible = false, is_admissible;
struct osl_object playlist_def;
char *new_path;
const struct osl_row *row = data;
- if (event == AUDIO_FILE_RENAME) {
- ret = row_belongs_to_score_table(row, NULL);
- if (ret < 0)
- return ret;
- was_admissible = ret;
- }
+ if (event == AUDIO_FILE_RENAME)
+ was_admissible = row_belongs_to_score_table(row);
ret = get_audio_file_path_of_row(row, &new_path);
if (ret < 0)
return ret;
int playlists_event_handler(enum afs_events event,
__a_unused struct para_buffer *pb, void *data)
{
- int ret;
struct afsi_change_event_data *aced = data;
if (!current_playlist.name)
case AUDIO_FILE_ADD:
return handle_audio_file_event(event, data);
case AUDIO_FILE_REMOVE:
- ret = row_belongs_to_score_table(data, NULL);
- if (ret < 0)
- return ret;
- if (!ret)
+ if (!row_belongs_to_score_table(data))
return 1;
current_playlist.length--;
return score_delete(data);
.column_descriptions = score_cols
};
-/**
- * Compute the number of files in score table.
- *
- * \param num Result is returned here.
- *
- * \return Positive on success, negative on errors.
- */
-int get_num_admissible_files(unsigned *num)
-{
- return osl(osl_get_num_rows(score_table, num));
-}
-
/* On errors (negative return value) the content of score is undefined. */
static int get_score_of_row(void *score_row, long *score)
{
return ret;
}
-static int get_nth_score(unsigned n, long *score)
-{
- struct osl_row *row;
- int ret = osl(osl_get_nth_row(score_table, SCORECOL_SCORE, n, &row));
-
- if (ret < 0)
- return ret;
- return get_score_of_row(row, score);
-}
-
/**
* Replace a row of the score table.
*
*/
int score_update(const struct osl_row *aft_row, long percent)
{
- struct osl_row *row;
+ struct osl_row *row, *rrow; /* score row, reference row */
long new_score;
unsigned n, new_pos;
struct osl_object obj = {.data = (struct osl_row *)aft_row,
return 1;
if (ret < 0)
return ret;
- ret = get_num_admissible_files(&n);
+ ret = osl(osl_get_num_rows(score_table, &n));
if (ret < 0)
return ret;
new_pos = 1 + (n - 1) * percent / 100;
- ret = get_nth_score(new_pos, &new_score);
+ ret = osl(osl_get_nth_row(score_table, SCORECOL_SCORE, new_pos, &rrow));
+ if (ret < 0)
+ return ret;
+ ret = get_score_of_row(rrow, &new_score);
if (ret < 0)
return ret;
new_score--;
}
/**
- * Loop over all files in the score table.
- *
- * \param data A pointer to arbitrary data.
- * \param func Function to be called for each admissible file.
+ * Call the given function for each row of the score table.
*
- * \return The return value of the underlying call to osl_rbtree_loop().
+ * \param func Callback, called once per row.
+ * \param data Passed verbatim to the callback.
*
- * This is used for the ls command. The \a data parameter is passed as the
- * second argument to \a func.
+ * \return The return value of the underlying call to osl_rbtree_loop(). The
+ * loop terminates early if the callback returns negative.
*/
-int admissible_file_loop(void *data, osl_rbtree_loop_func *func)
+int score_loop(osl_rbtree_loop_func *func, void *data)
{
return osl(osl_rbtree_loop(score_table, SCORECOL_SCORE, data, func));
}
* Find out whether an audio file is contained in the score table.
*
* \param aft_row The row of the audio file table.
- * \param rank Result pointer
*
- * \return Positive, if \a aft_row belongs to the audio file table,
- * zero if not, negative on errors. If \a aft_row was found, and \a rank
- * is not \p NULL, the rank of \a aft_row is returned in \a rank.
+ * \return If the lookup operation fails for any other reason than "not found",
+ * the function aborts the current process (afs), since this is considered a
+ * fatal error that should never happen.
*/
-int row_belongs_to_score_table(const struct osl_row *aft_row, unsigned *rank)
+bool row_belongs_to_score_table(const struct osl_row *aft_row)
{
struct osl_row *score_row;
int ret = get_score_row_from_aft_row(aft_row, &score_row);
if (ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND))
- return 0;
- if (ret < 0)
- return ret;
- if (!rank)
- return 1;
- ret = osl(osl_get_rank(score_table, score_row, SCORECOL_SCORE, rank));
- if (ret < 0)
- return ret;
- return 1;
+ return false;
+ assert(ret >= 0);
+ return true;
}
static void score_close(void)
static int score_open(__a_unused const char *dir)
{
- score_table_desc.dir = NULL; /* this table has only volatile columns */
- return osl(osl_open_table(&score_table_desc, &score_table));
+ assert(osl_open_table(&score_table_desc, &score_table) >= 0);
+ return 1;
}
/**
* Remove all entries from the score table, but keep the table open.
- *
- * \return Standard.
*/
-int clear_score_table(void)
+void score_clear(void)
{
score_close();
- return score_open(NULL);
+ score_open(NULL);
}
-static int score_event_handler(__a_unused enum afs_events event,
- __a_unused struct para_buffer *pb, __a_unused void *data)
-{
- return 1;
-}
-
-/**
- * Initialize the scoring subsystem.
- *
- * \param t The members of \a t are filled in by the function.
- */
-void score_init(struct afs_table *t)
-{
- t->name = score_table_desc.name;
- t->open = score_open;
- t->close = score_close;
- t->event_handler = score_event_handler;
-}
+/** The score table stores (aft row, score) pairs in memory. */
+const struct afs_table_operations score_ops = {
+ .open = score_open,
+ .close = score_close,
+};
errno = save_errno;
return;
}
- if (ret < 0)
- PARA_EMERG_LOG("%s\n", strerror(errno));
- else
- PARA_EMERG_LOG("short write to signal pipe\n");
+ /*
+ * This is a fatal error which should never happen. We must not call
+ * PARA_XXX_LOG() here because this might acquire the log mutex which
+ * is already taken by the main program if the interrupt occurs while a
+ * log message is being printed. The mutex will not be released as long
+ * as this signal handler is running, so a deadlock ensues.
+ */
exit(EXIT_FAILURE);
}
uint32_t port = RECV_CMD_OPT_UINT32_VAL(UDP, PORT, lpr);
int ret;
- ret = makesock(IPPROTO_UDP, 1, host, port, NULL);
+ ret = makesock(IPPROTO_UDP, true /* passive */, host, port, NULL);
if (ret < 0)
return ret;
rn->fd = ret;
return ret;
port = scd->port > 0 ? scd->port : OPT_UINT32_VAL(UDP_DEFAULT_PORT);
- ret = para_connect_simple(IPPROTO_UDP, scd->host, port);
+ ret = para_connect(IPPROTO_UDP, scd->host, port);
if (ret < 0)
return ret;
sc->private_data = ut;
sc->fd = -1;
- ret = para_connect_simple(IPPROTO_UDP, scd->host, scd->port);
+ ret = para_connect(IPPROTO_UDP, scd->host, scd->port);
if (ret < 0)
goto err;
sc->fd = ret;
}
/**
- * Main sending function.
- *
- * This function gets called from vss_post_monitor(). It checks whether the next
- * chunk of data should be pushed out. It obtains a pointer to the data to be
- * sent out as well as its length from mmd->afd.afhi. This information is then
- * passed to each supported sender's send() function as well as to the send()
- * functions of each registered fec client.
+ * If the next chunk needs to be sent, pass a pointer to the chunk data to all
+ * registered fec clients and to each sender's ->send() method.
*/
static void vss_send(struct vss_task *vsst)
{