- Version 1.0 of the openssl library has been deprecated. A warning
is printed at compile-time on systems which have this outdated version
because it will no longer be supported once paraslash-0.8.0 comes out.
+- A spring cleanup for the senescent code in fd.c.
+- The --admissible option of the ls command now takes an optional
+ argument. When invoked like --admissible=m/foo, only files which are
+ admissible with respect to mood foo are listed.
+- The select server command is now quiet by default, The new --verbose
+ option can be used to show information about the newly loaded mood
+ or playlist.
+- The ls server command gained the --limit option to force a limit
+ on the number of files listed.
+- Cleanup of the openssl-specific code.
Downloads:
[tarball](./releases/paraslash-git.tar.xz)
int ret;
char *msg;
- if (!arg) {
- ret = mood_load(NULL, &msg);
+ if (!arg) { /* load dummy mood */
+ ret = mood_load(NULL, NULL, &msg);
mode = PLAY_MODE_MOOD;
} else if (!strncmp(arg, "p/", 2)) {
- ret = playlist_load(arg + 2, &msg);
+ ret = playlist_load(arg + 2, NULL, &msg);
mode = PLAY_MODE_PLAYLIST;
} else if (!strncmp(arg, "m/", 2)) {
- ret = mood_load(arg + 2, &msg);
+ ret = mood_load(arg + 2, NULL, &msg);
mode = PLAY_MODE_MOOD;
} else {
ret = -ERRNO_TO_PARA_ERROR(EINVAL);
- msg = make_message("%s: parse error", arg);
+ msg = make_message("%s: parse error\n", arg);
}
if (pb)
para_printf(pb, "%s", msg);
PARA_INFO_LOG("afs_database dir %s\n", database_dir);
}
-static int make_database_dir(void)
-{
- int ret;
-
- get_database_dir();
- ret = para_mkdir(database_dir, 0777);
- if (ret >= 0 || ret == -ERRNO_TO_PARA_ERROR(EEXIST))
- return 1;
- return ret;
-}
-
static int open_afs_tables(void)
{
int i, ret;
}
ret = schedule(&s);
sched_shutdown(&s);
- mood_unload();
+ mood_unload(NULL);
+ playlist_unload(NULL);
out_close:
close_afs_tables();
out:
const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT);
const char *arg;
int ret;
+ struct para_buffer *pbout;
ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr);
assert(ret >= 0);
arg = lls_input(0, aca->lpr);
+ pbout = SERVER_CMD_OPT_GIVEN(SELECT, VERBOSE, aca->lpr)?
+ &aca->pbout : NULL;
score_clear();
if (current_play_mode == PLAY_MODE_MOOD)
- mood_unload();
+ mood_unload(NULL);
else
- playlist_unload();
- ret = activate_mood_or_playlist(arg, &aca->pbout);
+ playlist_unload(NULL);
+ ret = activate_mood_or_playlist(arg, 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);
+ ret2 = activate_mood_or_playlist(current_mop, 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);
+ activate_mood_or_playlist(NULL, pbout);
free_lpr:
lls_free_parse_result(aca->lpr, cmd);
return ret;
.size = sizeof(table_mask)};
unsigned num_inputs = lls_num_inputs(lpr);
- ret = make_database_dir();
+ get_database_dir();
+ ret = para_mkdir(database_dir);
if (ret < 0)
return ret;
if (num_inputs > 0) {
/* score */
extern const struct afs_table_operations score_ops;
-int score_loop(osl_rbtree_loop_func *func, void *data);
+void score_open(struct osl_table **result);
+void score_close(struct osl_table *t);
+int score_loop(osl_rbtree_loop_func *func, struct osl_table *t, 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_add(const struct osl_row *aft_row, long score, struct osl_table *t);
int score_update(const struct osl_row *aft_row, long new_score);
int score_delete(const struct osl_row *aft_row);
void score_clear(void);
void free_status_items(void);
/* mood */
-int mood_load(const char *mood_name, char **msg);
-void mood_unload(void);
+struct mood_instance;
+int mood_load(const char *mood_name, struct mood_instance **result, char **msg);
+int mood_loop(struct mood_instance *m, osl_rbtree_loop_func *func, void *data);
+void mood_unload(struct mood_instance *m);
int mood_check_callback(struct afs_callback_arg *aca);
/* playlist */
-int playlist_load(const char *name, char **msg);
-void playlist_unload(void);
+struct playlist_instance;
+int playlist_load(const char *name, struct playlist_instance **result, char **msg);
+int playlist_loop(struct playlist_instance *pi, osl_rbtree_loop_func *func, void *data);
+void playlist_unload(struct playlist_instance *pi);
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 */
#include <sys/mman.h>
#include <fnmatch.h>
#include <sys/shm.h>
+#include <dirent.h>
#include <osl.h>
#include <lopsub.h>
return ret;
}
+static int mop_loop(const char *arg, struct afs_callback_arg *aca,
+ struct ls_options *opts)
+{
+ int ret;
+ char *msg;
+
+ if (!arg || strcmp(arg, ".") == 0)
+ return score_loop(prepare_ls_row, NULL, opts);
+ if (!strncmp(arg, "m/", 2)) {
+ struct mood_instance *m;
+ ret = mood_load(arg + 2, &m, &msg);
+ if (ret < 0)
+ afs_error(aca, "%s", msg);
+ free(msg);
+ if (ret < 0)
+ return ret;
+ ret = mood_loop(m, prepare_ls_row, opts);
+ mood_unload(m);
+ return ret;
+ }
+ if (!strncmp(arg, "p/", 2)) {
+ struct playlist_instance *pi;
+ ret = playlist_load(arg + 2, &pi, &msg);
+ if (ret < 0)
+ afs_error(aca, "%s", msg);
+ free(msg);
+ if (ret < 0)
+ return ret;
+ ret = playlist_loop(pi, prepare_ls_row, opts);
+ playlist_unload(pi);
+ return ret;
+ }
+ afs_error(aca, "bad mood/playlist specifier: %s\n", arg);
+ return -ERRNO_TO_PARA_ERROR(EINVAL);
+}
+
static int com_ls_callback(struct afs_callback_arg *aca)
{
const struct lls_command *cmd = SERVER_CMD_CMD_PTR(LS);
struct ls_options *opts = aca->query.data;
- int i = 0, ret;
+ int ret;
time_t current_time;
- const struct lls_opt_result *r_r;
+ const struct lls_opt_result *r_r, *r_a;
+ uint32_t limit, k, n;
ret = lls_deserialize_parse_result(
(char *)aca->query.data + sizeof(*opts), cmd, &opts->lpr);
assert(ret >= 0);
r_r = SERVER_CMD_OPT_RESULT(LS, REVERSE, opts->lpr);
-
+ r_a = SERVER_CMD_OPT_RESULT(LS, ADMISSIBLE, opts->lpr);
aca->pbout.flags = (opts->mode == LS_MODE_PARSER)? PBF_SIZE_PREFIX : 0;
- if (admissible_only(opts))
- ret = score_loop(prepare_ls_row, opts);
- else
+ if (admissible_only(opts)) {
+ const char *arg = lls_string_val(0, r_a);
+ ret = mop_loop(arg, aca, opts);
+ } else
ret = osl(osl_rbtree_loop(audio_file_table, AFTCOL_PATH, opts,
prepare_ls_row));
if (ret < 0)
goto out;
- if (opts->num_matching_paths == 0) {
+ n = opts->num_matching_paths;
+ if (n == 0) {
ret = lls_num_inputs(opts->lpr) > 0? -E_NO_MATCH : 0;
goto out;
}
if (ret < 0)
goto out;
time(¤t_time);
- if (lls_opt_given(r_r))
- for (i = opts->num_matching_paths - 1; i >= 0; i--) {
- ret = print_list_item(opts->data_ptr[i], opts,
- &aca->pbout, current_time);
- if (ret < 0)
- goto out;
- }
- else
- for (i = 0; i < opts->num_matching_paths; i++) {
- ret = print_list_item(opts->data_ptr[i], opts,
- &aca->pbout, current_time);
- if (ret < 0)
- goto out;
- }
+ limit = SERVER_CMD_UINT32_VAL(LS, LIMIT, opts->lpr);
+ for (k = 0; k < n && (limit == 0 || k < limit); k++) {
+ uint32_t idx = lls_opt_given(r_r)? n - 1 - k : k;
+ ret = print_list_item(opts->data_ptr[idx], opts, &aca->pbout,
+ current_time);
+ if (ret < 0)
+ goto out;
+ }
out:
lls_free_parse_result(opts->lpr, cmd);
free(opts->data);
return send_ret;
}
+/*
+ * Call back once for each regular file below a directory.
+ *
+ * Traverse the given directory recursively and call the supplied callback for
+ * each regular file encountered. The first argument to the callback will be
+ * the path to the regular file and the second argument will be the data
+ * pointer. All file types except regular files and directories are ignored. In
+ * particular, symlinks are not followed. Subdirectories are ignored silently
+ * if the calling process has insufficient access permissions.
+ */
+static int for_each_file_in_dir(const char *dirname,
+ int (*func)(const char *, void *), void *data)
+{
+ int ret;
+ DIR *dir;
+ struct dirent *entry;
+
+ dir = opendir(dirname);
+ if (!dir)
+ return errno == EACCES? 1 : -ERRNO_TO_PARA_ERROR(errno);
+ /* scan cwd recursively */
+ while ((entry = readdir(dir))) {
+ char *tmp;
+ struct stat s;
+
+ if (!strcmp(entry->d_name, "."))
+ continue;
+ if (!strcmp(entry->d_name, ".."))
+ continue;
+ tmp = make_message("%s/%s", dirname, entry->d_name);
+ ret = 0;
+ if (lstat(tmp, &s) != -1) {
+ if (S_ISREG(s.st_mode))
+ ret = func(tmp, data);
+ else if (S_ISDIR(s.st_mode))
+ ret = for_each_file_in_dir(tmp, func, data);
+ }
+ free(tmp);
+ if (ret < 0)
+ goto out;
+ }
+ ret = 1;
+out:
+ closedir(dir);
+ return ret;
+}
+
static int com_add(struct command_context *cc, struct lls_parse_result *lpr)
{
int i, ret;
*/
if (strcmp(name, "udp") == 0 || strcmp(name, "dccp") == 0) {
tmp = para_strdup("fecdec");
- add_filter(i, tmp);
+ ret = add_filter(i, tmp);
free(tmp);
if (ret < 0)
goto out;
/** The list of all status items used by para_{server,audiod,gui}. */
const char *status_item_list[] = {STATUS_ITEMS};
-static void dump_stat_client_list(void)
-{
- struct stat_client *sc;
-
- list_for_each_entry(sc, &client_list, node)
- PARA_INFO_LOG("stat client on fd %d\n", sc->fd);
-}
/**
* Add a status client to the list.
*
if (parser_friendly)
new_client->flags = SCF_PARSER_FRIENDLY;
para_list_add(&new_client->node, &client_list);
- dump_stat_client_list();
num_clients++;
return 1;
}
continue;
/* write error or short write */
close_stat_client(sc);
- dump_stat_client_list();
}
free(pb.buf);
free(pfpb.buf);
if (CLIENT_OPT_GIVEN(KEY_FILE, lpr))
kf = para_strdup(CLIENT_OPT_STRING_VAL(KEY_FILE, lpr));
else {
+ struct stat statbuf;
kf = make_message("%s/.paraslash/key.%s", home, user);
- if (!file_exists(kf)) {
+ if (stat(kf, &statbuf) != 0) { /* assume file does not exist */
free(kf);
kf = make_message("%s/.ssh/id_rsa", home);
}
}
/**
- * Write an array of buffers to a file descriptor.
+ * Write an array of buffers, handling non-fatal errors.
*
- * \param fd The file descriptor.
+ * \param fd The file descriptor to write to.
* \param iov Pointer to one or more buffers.
* \param iovcnt The number of buffers.
*
- * EAGAIN/EWOULDBLOCK is not considered a fatal error condition. For example
- * DCCP CCID3 has a sending wait queue which fills up and is emptied
- * asynchronously. The EAGAIN case means that there is currently no space in
- * the wait queue, but this can change at any moment.
+ * EAGAIN, EWOULDBLOCK and EINTR are not considered error conditions. If a
+ * write operation fails with EAGAIN or EWOULDBLOCK, the number of bytes that
+ * have been written so far is returned. In the EINTR case the operation is
+ * retried. Short writes are handled by issuing a subsequent write operation
+ * for the remaining part.
*
* \return Negative on fatal errors, number of bytes written else.
*
* For blocking file descriptors, this function returns either the sum of all
- * buffer sizes, or the error code of the fatal error that caused the last
- * write call to fail.
+ * buffer sizes or a negative error code which indicates the fatal error that
+ * caused a write call to fail.
*
- * For nonblocking file descriptors there is a third possibility: Any positive
- * return value less than the sum of the buffer sizes indicates that some bytes
- * have been written but the next write would block.
+ * For nonblocking file descriptors there is a third possibility: Any
+ * non-negative return value less than the sum of the buffer sizes indicates
+ * that a write operation returned EAGAIN/EWOULDBLOCK.
*
* \sa writev(2), \ref xwrite().
*/
}
/**
- * Write all data to a file descriptor.
+ * Write to a file descriptor, fail on short writes.
*
* \param fd The file descriptor.
- * \param buf The buffer to be sent.
- * \param len The length of \a buf.
+ * \param buf The buffer to be written.
+ * \param len The length of the buffer.
*
- * This is like \ref xwrite() but returns \p -E_SHORT_WRITE if not
- * all data could be written.
+ * For blocking file descriptors this function behaves identical to \ref
+ * xwrite(). For non-blocking file descriptors it returns -E_SHORT_WRITE
+ * (rather than a value less than len) if not all data could be written.
*
* \return Number of bytes written on success, negative error code else.
*/
}
/**
- * Write a buffer given by a format string.
+ * A fprintf-like function for raw file descriptors.
+ *
+ * This function creates a string buffer according to the given format and
+ * writes this buffer to a file descriptor.
*
* \param fd The file descriptor.
* \param fmt A format string.
*
+ * The difference to fprintf(3) is that the first argument is a file
+ * descriptor, not a FILE pointer. This function does not rely on stdio.
+ *
* \return The return value of the underlying call to \ref write_all().
+ *
+ * \sa fprintf(3), \ref xvasprintf().
*/
__printf_2_3 int write_va_buffer(int fd, const char *fmt, ...)
{
}
/**
- * Read a buffer and check its content for a pattern.
- *
- * \param fd The file descriptor to receive from.
- * \param pattern The expected pattern.
- * \param bufsize The size of the internal buffer.
+ * Read a buffer and compare its contents to a string, ignoring case.
*
- * This function tries to read at most \a bufsize bytes from the non-blocking
- * file descriptor \a fd. If at least \p strlen(\a pattern) bytes have been
- * received, the beginning of the received buffer is compared with \a pattern,
- * ignoring case.
+ * \param fd The file descriptor to read from.
+ * \param expectation The expected string to compare to.
*
- * \return Positive if \a pattern was received, negative on errors, zero if no data
- * was available to read.
+ * The given file descriptor is expected to be in non-blocking mode. The string
+ * comparison is performed using strncasecmp(3).
*
- * \sa \ref read_nonblock(), \sa strncasecmp(3).
+ * \return Zero if no data was available, positive if a buffer was read whose
+ * contents compare as equal to the expected string, negative otherwise.
+ * Possible errors: (a) not enough data was read, (b) the buffer contents
+ * compared as non-equal, (c) a read error occurred. In the first two cases,
+ * -E_READ_PATTERN is returned. In the read error case the (negative) return
+ * value of the underlying call to \ref read_nonblock() is returned.
*/
-int read_pattern(int fd, const char *pattern, size_t bufsize)
+int read_and_compare(int fd, const char *expectation)
{
- size_t n, len;
- char *buf = alloc(bufsize + 1);
- int ret = read_nonblock(fd, buf, bufsize, &n);
+ size_t n, len = strlen(expectation);
+ char *buf = alloc(len + 1);
+ int ret = read_nonblock(fd, buf, len, &n);
- buf[n] = '\0';
if (ret < 0)
goto out;
+ buf[n] = '\0';
ret = 0;
if (n == 0)
goto out;
ret = -E_READ_PATTERN;
- len = strlen(pattern);
if (n < len)
goto out;
- if (strncasecmp(buf, pattern, len) != 0)
+ if (strncasecmp(buf, expectation, len) != 0)
goto out;
ret = 1;
out:
- if (ret < 0) {
- PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
- PARA_NOTICE_LOG("recvd %zu bytes: %s\n", n, buf);
- }
free(buf);
return ret;
}
-/**
- * Check whether a file exists.
- *
- * \param fn The file name.
- *
- * \return True iff file exists.
- */
-bool file_exists(const char *fn)
-{
- struct stat statbuf;
-
- return !stat(fn, &statbuf);
-}
-
/**
* Set a file descriptor to blocking mode.
*
}
/**
- * Wrapper for chdir(2).
- *
- * \param path The specified directory.
+ * Create a directory, don't fail if it already exists.
*
- * \return Standard.
- */
-int para_chdir(const char *path)
-{
- int ret = chdir(path);
-
- if (ret >= 0)
- return 1;
- return -ERRNO_TO_PARA_ERROR(errno);
-}
-
-/**
- * Save the cwd and open a given directory.
- *
- * \param dirname Path to the directory to open.
- * \param dir Result pointer.
- * \param cwd File descriptor of the current working directory.
- *
- * \return Standard.
- *
- * Opening the current directory (".") and calling fchdir() to return is
- * usually faster and more reliable than saving cwd in some buffer and calling
- * chdir() afterwards.
- *
- * If \a cwd is not \p NULL "." is opened and the resulting file descriptor is
- * stored in \a cwd. If the function returns success, and \a cwd is not \p
- * NULL, the caller must close this file descriptor (probably after calling
- * fchdir(*cwd)).
- *
- * On errors, the function undos everything, so the caller needs neither close
- * any files, nor change back to the original working directory.
+ * \param path Name of the directory to create.
*
- * \sa getcwd(3).
+ * This function passes the fixed mode value 0777 to mkdir(3) (which consults
+ * the file creation mask and restricts this value).
*
+ * \return Zero if the path already existed as a directory or as a symbolic
+ * link which leads to a directory, one if the path did not exist and the
+ * directory has been created successfully, negative error code else.
*/
-static int para_opendir(const char *dirname, DIR **dir, int *cwd)
+int para_mkdir(const char *path)
{
- int ret;
+ /*
+ * We call opendir(3) rather than relying on stat(2) because this way
+ * we don't need extra code to get the symlink case right.
+ */
+ DIR *dir = opendir(path);
- *dir = NULL;
- if (cwd) {
- ret = para_open(".", O_RDONLY, 0);
- if (ret < 0)
- return ret;
- *cwd = ret;
- }
- ret = para_chdir(dirname);
- if (ret < 0)
- goto close_cwd;
- *dir = opendir(".");
- if (*dir)
- return 1;
- ret = -ERRNO_TO_PARA_ERROR(errno);
- /* Ignore return value of fchdir() and close(). We're busted anyway. */
- if (cwd) {
- int __a_unused ret2 = fchdir(*cwd); /* STFU, gcc */
+ if (dir) {
+ closedir(dir);
+ return 0;
}
-close_cwd:
- if (cwd)
- close(*cwd);
- return ret;
-}
-
-/**
- * A wrapper for mkdir(2).
- *
- * \param path Name of the directory to create.
- * \param mode The permissions to use.
- *
- * \return Standard.
- */
-int para_mkdir(const char *path, mode_t mode)
-{
- if (!mkdir(path, mode))
- return 1;
- return -ERRNO_TO_PARA_ERROR(errno);
+ if (errno != ENOENT)
+ return -ERRNO_TO_PARA_ERROR(errno);
+ return mkdir(path, 0777) == 0? 1 : -ERRNO_TO_PARA_ERROR(errno);
}
/**
* \param start The start address of the memory mapping.
* \param length The size of the mapping.
*
- * \return Standard.
+ * If NULL is passed as the start address, the length value is ignored and the
+ * function does nothing.
+ *
+ * \return Zero if NULL was passed, one if the memory area was successfully
+ * unmapped, a negative error code otherwise.
*
* \sa munmap(2), \ref mmap_full_file().
*/
int para_munmap(void *start, size_t length)
{
- int err;
-
if (!start)
return 0;
if (munmap(start, length) >= 0)
return 1;
- err = errno;
- PARA_ERROR_LOG("munmap (%p/%zu) failed: %s\n", start, length,
- strerror(err));
- return -ERRNO_TO_PARA_ERROR(err);
+ return -ERRNO_TO_PARA_ERROR(errno);
}
/**
}
}
}
-
-/**
- * Traverse the given directory recursively.
- *
- * \param dirname The directory to traverse.
- * \param func The function to call for each entry.
- * \param private_data Pointer to an arbitrary data structure.
- *
- * For each regular file under \a dirname, the supplied function \a func is
- * called. The full path of the regular file and the \a private_data pointer
- * are passed to \a func. Directories for which the calling process has no
- * permissions to change to are silently ignored.
- *
- * \return Standard.
- */
-int for_each_file_in_dir(const char *dirname,
- int (*func)(const char *, void *), void *private_data)
-{
- DIR *dir;
- struct dirent *entry;
- int cwd_fd, ret = para_opendir(dirname, &dir, &cwd_fd);
-
- if (ret < 0)
- return ret == -ERRNO_TO_PARA_ERROR(EACCES)? 1 : ret;
- /* scan cwd recursively */
- while ((entry = readdir(dir))) {
- mode_t m;
- char *tmp;
- struct stat s;
-
- if (!strcmp(entry->d_name, "."))
- continue;
- if (!strcmp(entry->d_name, ".."))
- continue;
- if (lstat(entry->d_name, &s) == -1)
- continue;
- m = s.st_mode;
- if (!S_ISREG(m) && !S_ISDIR(m))
- continue;
- tmp = make_message("%s/%s", dirname, entry->d_name);
- if (!S_ISDIR(m)) {
- ret = func(tmp, private_data);
- free(tmp);
- if (ret < 0)
- goto out;
- continue;
- }
- /* directory */
- ret = for_each_file_in_dir(tmp, func, private_data);
- free(tmp);
- if (ret < 0)
- goto out;
- }
- ret = 1;
-out:
- closedir(dir);
- if (fchdir(cwd_fd) < 0 && ret >= 0)
- ret = -ERRNO_TO_PARA_ERROR(errno);
- close(cwd_fd);
- return ret;
-}
int xrename(const char *oldpath, const char *newpath);
int write_all(int fd, const char *buf, size_t len);
__printf_2_3 int write_va_buffer(int fd, const char *fmt, ...);
-bool file_exists(const char *);
int xpoll(struct pollfd *fds, nfds_t nfds, int timeout);
__must_check int mark_fd_nonblocking(int fd);
__must_check int mark_fd_blocking(int fd);
int para_mmap(size_t length, int prot, int flags, int fd, void *map);
int para_open(const char *path, int flags, mode_t mode);
-int para_mkdir(const char *path, mode_t mode);
-int para_chdir(const char *path);
+int para_mkdir(const char *path);
int mmap_full_file(const char *filename, int open_mode, void **map,
size_t *size, int *fd_ptr);
int para_munmap(void *start, size_t length);
void valid_fd_012(void);
int readv_nonblock(int fd, struct iovec *iov, int iovcnt, size_t *num_bytes);
int read_nonblock(int fd, void *buf, size_t sz, size_t *num_bytes);
-int read_pattern(int fd, const char *pattern, size_t bufsize);
+int read_and_compare(int fd, const char *expectation);
int xwrite(int fd, const char *buf, size_t len);
int xwritev(int fd, struct iovec *iov, int iovcnt);
-int for_each_file_in_dir(const char *dirname,
- int (*func)(const char *, void *), void *private_data);
+
/**
* Write a \p NULL-terminated buffer.
*
return 0;
}
if (phd->status == HTTP_SENT_GET_REQUEST) {
- ret = read_pattern(rn->fd, HTTP_OK_MSG, strlen(HTTP_OK_MSG));
+ ret = read_and_compare(rn->fd, HTTP_OK_MSG);
if (ret < 0) {
PARA_ERROR_LOG("did not receive HTTP OK message\n");
goto out;
case HTTP_STREAMING: /* nothing to do */
break;
case HTTP_CONNECTED: /* need to recv get request */
- ret = read_pattern(sc->fd, HTTP_GET_MSG, MAXLINE);
+ ret = read_and_compare(sc->fd, HTTP_GET_MSG);
if (ret < 0)
phsd->status = HTTP_INVALID_GET_REQUEST;
else if (ret > 0) {
also given), chunk time and chunk offsets.
[/help]
+ [option limit]
+ short_opt = L
+ summary = list at most this many files
+ arg_type = uint32
+ arg_info = required_arg
+ typestr = num
+ [help]
+ An argument of zero means "unlimited". This is also the default which
+ applies if the option is not given.
+ [/help]
[option basename]
short_opt = b
summary = list and match basenames only
[option admissible]
short_opt = a
summary = list only admissible files
+ arg_type = string
+ arg_info = optional_arg
+ typestr = specifier/name
+ default_val = .
[help]
- List only files which are admissible with respect to the current mood
- or playlist.
+ If the optional argument is supplied, it must be of the form "p/foo"
+ or "m/bar" (which refer to the playlist named "foo" and the mood named
+ "bar", respectively). The command then restricts its output to the set
+ of files which are admissible with respect to the thusly identified
+ mood or playlist.
+
+ If no argument is given, or if the argument is the special value ".",
+ the current mood or playlist is assumed.
[/help]
[option reverse]
short_opt = r
activates the mood named 'foo'.
[/description]
+ [option verbose]
+ short_opt = v
+ summary = print information about the loaded mood or playlist
[subcommand sender]
purpose = control paraslash senders
struct mp_context *parser_context;
/** To compute the score. */
struct afs_statistics stats;
+ /** NULL means to operate on the global score table. */
+ struct osl_table *score_table;
};
/*
if (!m)
return;
mp_shutdown(m->parser_context);
+ if (m->score_table)
+ score_close(m->score_table);
free(m->name);
free(m);
}
}
static int add_to_score_table(const struct osl_row *aft_row,
- const struct afs_statistics *stats)
+ struct mood_instance *m)
{
long score;
struct afs_info afsi;
if (ret < 0)
return ret;
- score = compute_score(&afsi, stats);
- return score_add(aft_row, score);
+ score = compute_score(&afsi, &m->stats);
+ return score_add(aft_row, score, m->score_table);
}
static int delete_from_statistics_and_score_table(const struct osl_row *aft_row)
ret = add_afs_statistics(aft_row, ¤t_mood->stats);
if (ret < 0)
return ret;
- return add_to_score_table(aft_row, ¤t_mood->stats);
+ return add_to_score_table(aft_row, current_mood);
}
/* update score */
ret = get_afsi_of_row(aft_row, &afsi);
unsigned n = m->stats.num;
int mean_days, sigma_days;
+ if (n == 0)
+ return make_message("no admissible files\n");
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"
+ "correction factor ratio: %.2lf\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)
+ int_sqrt(m->stats.num_played_qd / n),
+ 86400.0 * m->stats.last_played_correction /
+ m->stats.num_played_correction
);
}
-/** Free all resources of the current mood, if any. */
-void mood_unload(void)
+/**
+ * Free all resources of a mood instance.
+ *
+ * \param m As obtained by \ref mood_load(). If NULL, unload the current mood.
+ *
+ * It's OK to call this with m == NULL even if no current mood is loaded.
+ */
+void mood_unload(struct mood_instance *m)
{
+ if (m)
+ return destroy_mood(m);
destroy_mood(current_mood);
current_mood = NULL;
}
}
/**
- * Change the current mood.
+ * Populate a score table with admissible files for the given mood.
+ *
+ * This consults the mood table to initialize the mood parser with the mood
+ * expression stored in the blob object which corresponds to the given name. A
+ * score table is allocated and populated with references to those entries of
+ * the audio file table which evaluate as admissible with respect to the mood
+ * expression. For each audio file a score value is computed and stored along
+ * with the file reference.
*
* \param mood_name The name of the mood to load.
+ * \param result Opaque, refers to the mood parser and the score table.
* \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.
+ * If the mood name is NULL, the dummy mood is loaded. This mood regards every
+ * audio file as admissible.
+ *
+ * A NULL result pointer instructs the function to operate on the current mood.
+ * That is, on the mood instance which is used by the server to select the next
+ * audio file for streaming. In this mode of operation, the mood which was
+ * active before the call, if any, is unloaded on success.
+ *
+ * If result is not NULL, the current mood is unaffected and *result points to
+ * an initialized mood instance on success. The caller can pass this reference
+ * to \ref mood_loop() to iterate over the admissible files, and should call
+ * \ref mood_unload() to free the mood instance afterwards.
*
* If the message pointer is not NULL, a suitable message is returned there in
* all cases. The caller must free this string.
*
- * \return The number of admissible files on success, negative on errors. It is
+ * \return The number of admissible files on success, negative on errors. On
+ * errors, the current mood remains unaffected even if result is NULL. It is
* not considered an error if no files are admissible.
*
- * \sa struct \ref afs_info::last_played, \ref mp_eval_row().
+ * \sa \ref mp_eval_row().
*/
-int mood_load(const char *mood_name, char **msg)
+int mood_load(const char *mood_name, struct mood_instance **result, char **msg)
{
int i, ret;
struct admissible_array aa = {.size = 0};
}
clock_get_realtime(&rnow);
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;
- }
+ if (result)
+ score_open(&aa.m->score_table);
for (i = 0; i < aa.m->stats.num; i++) {
- ret = add_to_score_table(aa.array[i], &aa.m->stats);
+ ret = add_to_score_table(aa.array[i], aa.m);
if (ret < 0) {
if (msg)
*msg = make_message(
if (msg)
*msg = get_statistics(aa.m, rnow.tv_sec);
ret = aa.m->stats.num;
- mood_unload();
- current_mood = aa.m;
+ if (result)
+ *result = aa.m;
+ else {
+ mood_unload(NULL);
+ current_mood = aa.m;
+ }
ret = 1;
out:
free(aa.array);
return ret;
}
+/**
+ * Iterate over the admissible files of a mood instance.
+ *
+ * This wrapper around \ref score_loop() is the mood counterpart of \ref
+ * playlist_loop().
+ *
+ * \param m Determines the score table to iterate. Must not be NULL.
+ * \param func See \ref score_loop().
+ * \param data See \ref score_loop().
+ *
+ * \return See \ref score_loop(), \ref playlist_loop().
+ */
+int mood_loop(struct mood_instance *m, osl_rbtree_loop_func *func, void *data)
+{
+ return score_loop(func, m->score_table, data);
+}
+
/*
* 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.
+ * This function is called on events which render the current set of admissible
+ * files invalid, for example if an attribute is removed from the attribute
+ * table.
*/
static int reload_current_mood(void)
{
current_mood->name : "(dummy)");
if (current_mood->name)
mood_name = para_strdup(current_mood->name);
- mood_unload();
- ret = mood_load(mood_name, NULL);
+ mood_unload(NULL);
+ ret = mood_load(mood_name, NULL, NULL);
free(mood_name);
return ret;
}
char *dot_para = make_message("%s/.paraslash", home);
free(home);
- ret = para_mkdir(dot_para, 0777);
+ ret = para_mkdir(dot_para);
/* warn, but otherwise ignore mkdir error */
- if (ret < 0 && ret != -ERRNO_TO_PARA_ERROR(EEXIST))
+ if (ret < 0)
PARA_WARNING_LOG("Can not create %s: %s\n", dot_para,
para_strerror(-ret));
history_file = make_message("%s/play.history", dot_para);
/** \file playlist.c Functions for loading and saving playlists. */
-/** Structure used for adding entries to a playlist. */
+/**
+ * The state of a playlist instance.
+ *
+ * A structure of this type is allocated and initialized at playlist load time.
+ */
struct playlist_instance {
/** The name of the playlist. */
char *name;
/** The number of entries currently in the playlist. */
unsigned length;
+ /** Contains all valid paths of the playlist. */
+ struct osl_table *score_table;
};
static struct playlist_instance current_playlist;
static int add_playlist_entry(char *path, void *data)
{
- struct playlist_instance *playlist = data;
+ struct playlist_instance *pi = data;
struct osl_row *aft_row;
int ret = aft_get_row_of_path(path, &aft_row);
PARA_NOTICE_LOG("%s: %s\n", path, para_strerror(-ret));
return 1;
}
- ret = score_add(aft_row, -playlist->length);
+ ret = score_add(aft_row, -pi->length, pi->score_table);
if (ret < 0) {
PARA_ERROR_LOG("failed to add %s: %s\n", path, para_strerror(-ret));
return ret;
}
- playlist->length++;
+ pi->length++;
return 1;
}
check_playlist));
}
-/** Free all resources of the current playlist, if any. */
-void playlist_unload(void)
+/**
+ * Free all resources of the given/current playlist.
+ *
+ * \param pi NULL means to unload the current playlist.
+ */
+void playlist_unload(struct playlist_instance *pi)
{
+ if (pi) {
+ score_close(pi->score_table);
+ free(pi->name);
+ free(pi);
+ return;
+ }
if (!current_playlist.name)
return;
+ score_clear();
free(current_playlist.name);
current_playlist.name = NULL;
current_playlist.length = 0;
* corresponding row of the audio file table is added to the score table.
*
* \param name The name of the playlist to load.
+ * \param result Opaque, refers to the underlying score table.
* \param msg Error message or playlist info is returned here.
*
* \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_load(const char *name, char **msg)
+int playlist_load(const char *name, struct playlist_instance **result, char **msg)
{
int ret;
- struct playlist_instance *playlist = ¤t_playlist;
+ struct playlist_instance *pi;
struct osl_object playlist_def;
- ret = pl_get_def_by_name(name, &playlist_def);
- if (ret < 0) {
- *msg = make_message("could not read playlist %s\n", name);
- return ret;
+ if (!name || !*name) {
+ if (msg)
+ *msg = make_message("empty playlist name\n");
+ return -ERRNO_TO_PARA_ERROR(EINVAL);
}
- playlist_unload();
+ ret = pl_get_def_by_name(name, &playlist_def);
+ if (ret < 0)
+ goto err;
+ pi = zalloc(sizeof(*pi));
+ if (result)
+ score_open(&pi->score_table);
ret = for_each_line(FELF_READ_ONLY, playlist_def.data,
- playlist_def.size, add_playlist_entry, playlist);
+ playlist_def.size, add_playlist_entry, pi);
osl_close_disk_object(&playlist_def);
if (ret < 0)
- goto err;
+ goto close_score_table;
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);
+ if (pi->length == 0)
+ goto close_score_table;
/* success */
- return current_playlist.length;
+ if (msg)
+ *msg = make_message("loaded playlist %s (%u files)\n", name,
+ pi->length);
+ pi->name = para_strdup(name);
+ if (result)
+ *result = pi;
+ else {
+ playlist_unload(NULL);
+ current_playlist = *pi;
+ }
+ return pi->length;
+close_score_table:
+ if (result)
+ score_close(pi->score_table);
+ free(pi);
err:
PARA_NOTICE_LOG("unable to load playlist %s\n", name);
- *msg = make_message("unable to load playlist %s\n", name);
+ if (msg)
+ *msg = make_message("unable to load playlist %s\n", name);
return ret;
}
+/**
+ * Iterate over all admissible audio files of a playlist instance.
+ *
+ * This wrapper around \ref score_loop() is the playlist counterpart of \ref
+ * mood_loop().
+ *
+ * \param pi Determines the score table to iterate. Must not be NULL.
+ * \param func See \ref score_loop().
+ * \param data See \ref score_loop().
+ *
+ * \return See \ref score_loop(), \ref mood_loop().
+ */
+int playlist_loop(struct playlist_instance *pi, osl_rbtree_loop_func *func, void *data)
+{
+ return score_loop(func, pi->score_table, data);
+}
+
static int search_path(char *path, void *data)
{
if (strcmp(path, data))
}
/* !was_admissible && is_admissible */
current_playlist.length++;
- return score_add(row, 0); /* play it immediately */
+ return score_add(row, 0, NULL); /* play it immediately */
}
/**
};
/* On errors (negative return value) the content of score is undefined. */
-static int get_score_of_row(void *score_row, long *score)
+static int get_score_of_row(struct osl_table *t, void *score_row, long *score)
{
struct osl_object obj;
- int ret = osl(osl_get_object(score_table, score_row, SCORECOL_SCORE, &obj));
+ int ret = osl(osl_get_object(t, score_row, SCORECOL_SCORE, &obj));
if (ret >= 0)
*score = *(long *)obj.data;
}
/**
- * Add an entry to the table of admissible files.
+ * Add a (row, score) pair to the score table.
*
- * \param aft_row The audio file to be added.
- * \param score The score for this file.
+ * \param aft_row Identifies the audio file to be added.
+ * \param score The score value of the audio file.
+ * \param t NULL means to operate on the currently active table.
*
* \return The return value of the underlying call to osl_add_row().
*/
-int score_add(const struct osl_row *aft_row, long score)
+int score_add(const struct osl_row *aft_row, long score, struct osl_table *t)
{
int ret;
struct osl_object score_objs[NUM_SCORE_COLUMNS];
*(long *)(score_objs[SCORECOL_SCORE].data) = score;
// PARA_DEBUG_LOG("adding %p\n", *(void **) (score_objs[SCORECOL_AFT_ROW].data));
- ret = osl(osl_add_row(score_table, score_objs));
+ ret = osl(osl_add_row(t? t : score_table, score_objs));
if (ret < 0) {
PARA_ERROR_LOG("%s\n", para_strerror(-ret));
free(score_objs[SCORECOL_SCORE].data);
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);
+ ret = get_score_of_row(score_table, rrow, &new_score);
if (ret < 0)
return ret;
new_score--;
struct osl_row **aft_row)
{
struct osl_object obj;
- int ret = get_score_of_row(score_row, score);
+ int ret = get_score_of_row(score_table, score_row, score);
if (ret < 0)
return ret;
return 1;
}
-static int get_score_row_from_aft_row(const struct osl_row *aft_row,
- struct osl_row **score_row)
+static int get_score_row_from_aft_row(struct osl_table *t,
+ const struct osl_row *aft_row, struct osl_row **score_row)
{
struct osl_object obj = {.data = (struct osl_row *)aft_row,
.size = sizeof(aft_row)};
- return osl(osl_get_row(score_table, SCORECOL_AFT_ROW, &obj, score_row));
+ return osl(osl_get_row(t, SCORECOL_AFT_ROW, &obj, score_row));
}
/**
* Call the given function for each row of the score table.
*
* \param func Callback, called once per row.
+ * \param t NULL means to use the currently active score table.
* \param data Passed verbatim to the callback.
*
* \return The return value of the underlying call to osl_rbtree_loop(). The
* loop terminates early if the callback returns negative.
*/
-int score_loop(osl_rbtree_loop_func *func, void *data)
+int score_loop(osl_rbtree_loop_func *func, struct osl_table *t, void *data)
{
- return osl(osl_rbtree_loop(score_table, SCORECOL_SCORE, data, func));
+ return osl(osl_rbtree_loop(t? t : score_table, SCORECOL_SCORE, data,
+ func));
}
/**
if (ret < 0)
return ret;
*aft_row = obj.data;
- return get_score_of_row(row, score);
+ return get_score_of_row(score_table, row, score);
}
/**
int score_delete(const struct osl_row *aft_row)
{
struct osl_row *score_row;
- int ret = get_score_row_from_aft_row(aft_row, &score_row);
+ int ret = get_score_row_from_aft_row(score_table, aft_row, &score_row);
if (ret < 0)
return ret;
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);
+ int ret = get_score_row_from_aft_row(score_table, aft_row, &score_row);
if (ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND))
return false;
return true;
}
-static void score_close(void)
+/**
+ * Free all volatile objects, then close the table.
+ *
+ * \param t As returned from \ref score_open().
+ *
+ * This either succeeds or terminates the calling process.
+ */
+void score_close(struct osl_table *t)
+{
+ assert(osl_close_table(t? t : score_table, OSL_FREE_VOLATILE) >= 0);
+}
+
+static void close_global_table(void)
{
- osl_close_table(score_table, OSL_FREE_VOLATILE);
- score_table = NULL;
+ score_close(NULL);
}
-static int score_open(__a_unused const char *dir)
+static int open_global_table(__a_unused const char *dir)
{
- assert(osl_open_table(&score_table_desc, &score_table) >= 0);
+ assert(osl(osl_open_table(&score_table_desc, &score_table)) >= 0);
return 1;
}
+/**
+ * Allocate a score table instance.
+ *
+ * \param result NULL means to open the currently active score table.
+ *
+ * Since the score table does no filesystem I/O, this function always succeeds.
+ * \sa \ref score_close().
+ */
+void score_open(struct osl_table **result)
+{
+ if (result)
+ assert(osl(osl_open_table(&score_table_desc, result)) >= 0);
+ else
+ open_global_table(NULL);
+}
+
/**
* Remove all entries from the score table, but keep the table open.
*/
void score_clear(void)
{
- score_close();
- score_open(NULL);
+ close_global_table();
+ open_global_table(NULL);
}
/** The score table stores (aft row, score) pairs in memory. */
const struct afs_table_operations score_ops = {
- .open = score_open,
- .close = score_close,
+ .open = open_global_table,
+ .close = close_global_table,
};
say_color()
{
+ local severity=$1
+
+ shift
if [[ "$o_nocolor" != "true" && -n "$1" ]]; then
export TERM=$ORIGINAL_TERM
- case "$1" in
+ case "$severity" in
error) tput $C_BOLD; tput $C_SETAF 1;;
skip) tput $C_SETAF 5;;
ok)
tput $C_SETAF 6;;
esac
fi
- shift
- printf "%s\n" "$*"
+ if [[ "$severity" == 'error' ]]; then
+ printf "%s\n" "$*" 1>&2
+ else
+ printf "%s\n" "$*"
+ fi
if [[ "$o_nocolor" != "true" && -n "$1" ]]; then
tput $C_SGR0
export TERM=dumb