From 44779382f0699be6b8daeeb25e7e1a2c40116d4b Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Tue, 23 Aug 2016 12:04:19 +0200 Subject: [PATCH] Convert para_play to lopsub. This changes para_play to use the lopsub library rather than gengetopt for parsing the command line and the config file. The old approach of permuting argv[] no longer works with lopsub because direct access to the array of non-option arguments is no longer possible. Therefore we introduce shuffle_map[], an array of indices which defaults to 1..n if --randomize is not given, and a random permutation of 1..n otherwise. The new get_playlist_file() helper returns the path to the input file determined by permuting the given index. The overall description for the manual page has also been improved slightly. Since m4/gengetopt/history_file.m4 has become unused, this file can be removed. --- Makefile.real | 4 +- configure.ac | 2 - m4/gengetopt/history_file.m4 | 12 --- m4/gengetopt/play.m4 | 48 --------- m4/lls/play.suite.m4 | 40 ++++++++ play.c | 193 ++++++++++++++++++++++------------- 6 files changed, 163 insertions(+), 136 deletions(-) delete mode 100644 m4/gengetopt/history_file.m4 delete mode 100644 m4/gengetopt/play.m4 create mode 100644 m4/lls/play.suite.m4 diff --git a/Makefile.real b/Makefile.real index 84872be3..e119919d 100644 --- a/Makefile.real +++ b/Makefile.real @@ -43,14 +43,14 @@ all_objs := $(sort $(recv_objs) $(filter_objs) $(client_objs) $(gui_objs) \ $(audiod_objs) $(audioc_objs) $(fade_objs) $(server_objs) \ $(write_objs) $(afh_objs) $(play_objs)) deps := $(addprefix $(dep_dir)/, $(filter-out %.cmdline.d, $(all_objs:.o=.d))) -converted_executables := audioc client fade +converted_executables := audioc client fade play unconverted_executables := $(filter-out $(converted_executables), $(executables)) audioc_objs += audioc.lsg.o audiod_objs += audiod_cmd.lsg.o recv_cmd.lsg.o client.lsg.o fade_objs += fade.lsg.o server_objs += server_cmd.lsg.o -play_objs += play_cmd.lsg.o recv_cmd.lsg.o +play_objs += play_cmd.lsg.o recv_cmd.lsg.o play.lsg.o recv_objs += recv_cmd.lsg.o client_objs += client.lsg.o diff --git a/configure.ac b/configure.ac index 909205e5..d592fba8 100644 --- a/configure.ac +++ b/configure.ac @@ -846,7 +846,6 @@ play_errlist_objs=" play fd sched - ggo buffer_tree time string @@ -880,7 +879,6 @@ play_cmdline_objs=" amp_filter prebuffer_filter file_write - play sync_filter " if test "$have_core_audio" = "yes"; then diff --git a/m4/gengetopt/history_file.m4 b/m4/gengetopt/history_file.m4 deleted file mode 100644 index 73e98a78..00000000 --- a/m4/gengetopt/history_file.m4 +++ /dev/null @@ -1,12 +0,0 @@ - -option "history-file" - -#~~~~~~~~~~~~~~~~~~~~~~ -"(default='DEFAULT_HISTORY_FILE')" -string typestr = "filename" -optional -details = " - If CURRENT_PROGRAM runs in interactive mode, it reads the history - file on startup. Upon exit, the in-memory history is appended - to the history file. -" - diff --git a/m4/gengetopt/play.m4 b/m4/gengetopt/play.m4 deleted file mode 100644 index 16875590..00000000 --- a/m4/gengetopt/play.m4 +++ /dev/null @@ -1,48 +0,0 @@ -args "--unamed-opts=audio_file --no-handle-version --conf-parser --no-handle-help" - -purpose "Command line audio player" - -description "para_play operates either in command mode or in insert -mode. In insert mode it presents a prompt and allows to enter commands -like stop, play, pause etc. In command mode the current audio file -is shown and the program reads single key strokes from stdin. Keys -may be mapped to commands. Whenever a mapped key is pressed, the -associated command is executed." - -include(header.m4) -define(CURRENT_PROGRAM,para_play) -define(DEFAULT_CONFIG_FILE,~/.paraslash/play.conf) -define(DEFAULT_HISTORY_FILE,~/.paraslash/play.history) - - -######################### -section "General options" -######################### - -include(loglevel.m4) -include(config_file.m4) -include(history_file.m4) - - -############################### -section "Options for para_play" -############################### - -option "randomize" z -#~~~~~~~~~~~~~~~~~~~ -"randomize playlist at startup." -flag off - -option "key-map" k -#~~~~~~~~~~~~~~~~~ -"Map key k to a command." - -string typestr = "key:command [args]" -optional -multiple -details = " - This option may be given multiple times, one for each key - mapping. Example: - 5:jmp 50 -" - diff --git a/m4/lls/play.suite.m4 b/m4/lls/play.suite.m4 new file mode 100644 index 00000000..0fbba0c0 --- /dev/null +++ b/m4/lls/play.suite.m4 @@ -0,0 +1,40 @@ +m4_define(PROGRAM, para_play) +m4_define(DEFAULT_HISTORY_FILE, ~/.paraslash/play.history) +m4_define(DEFAULT_CONFIG_FILE,~/.paraslash/play.conf) +[suite play] +version-string = GIT_VERSION() +[supercommand para_play] + purpose = command line audio player + non-opts-name = ... + [description] + para_play operates either in command mode or in insert mode. In + insert mode it presents a prompt and allows to enter commands like + stop, play, pause etc. In command mode the current audio file and the + playback position are shown and the program reads single key strokes + from stdin. Keys may be mapped to commands so that the configured + command is executed when a mapped key is pressed. + [/description] + m4_include(common-option-section.m4) + m4_include(help.m4) + m4_include(detailed-help.m4) + m4_include(version.m4) + m4_include(loglevel.m4) + m4_include(config-file.m4) + m4_include(history-file.m4) + m4_include(per-command-options-section.m4) + [option randomize] + short_opt = z + summary = randomize playlist at startup + [option key-map] + short_opt = k + summary = map a key to a command + arg_info = required_arg + arg_type = string + flag multiple + typestr = key:command [args] + [help] + This option may be given multiple times, one for each key + mapping. Example: + + --key-map 5:jmp 50 + [/help] diff --git a/play.c b/play.c index 4dab1cad..330c8b21 100644 --- a/play.c +++ b/play.c @@ -12,10 +12,10 @@ #include #include "recv_cmd.lsg.h" +#include "play_cmd.lsg.h" +#include "play.lsg.h" #include "para.h" #include "list.h" -#include "play.cmdline.h" -#include "play_cmd.lsg.h" #include "error.h" #include "ggo.h" #include "buffer_tree.h" @@ -42,6 +42,15 @@ /** Array of error strings. */ DEFINE_PARA_ERRLIST; +static struct lls_parse_result *play_lpr; + +#define CMD_PTR (lls_cmd(0, play_suite)) +#define OPT_RESULT(_name) \ + (lls_opt_result(LSG_PLAY_PARA_PLAY_OPT_ ## _name, play_lpr)) +#define OPT_GIVEN(_name) (lls_opt_given(OPT_RESULT(_name))) +#define OPT_UINT32_VAL(_name) (lls_uint32_val(0, OPT_RESULT(_name))) +#define OPT_STRING_VAL(_name) (lls_string_val(0, OPT_RESULT(_name))) + /** * Describes a request to change the state of para_play. * @@ -118,73 +127,105 @@ INIT_STDERR_LOGGING(loglevel); char *stat_item_values[NUM_STAT_ITEMS] = {NULL}; -/** Iterate over all files in the playlist. */ -#define FOR_EACH_PLAYLIST_FILE(i) for (i = 0; i < conf.inputs_num; i++) -static struct play_args_info conf; - static struct sched sched = {.max_fileno = 0}; static struct play_task play_task; #define AFH_RECV_CMD (lls_cmd(LSG_RECV_CMD_CMD_AFH, recv_cmd_suite)) #define AFH_RECV ((struct receiver *)lls_user_data(AFH_RECV_CMD)) -__noreturn static void print_help_and_die(void) +static unsigned *shuffle_map; + +static const char *get_playlist_file(unsigned idx) +{ + return lls_input(shuffle_map[idx], play_lpr); +} + +static void handle_help_flags(void) { - struct ggo_help help = DEFINE_GGO_HELP(play); - unsigned flags = conf.detailed_help_given? - GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS; + char *help; - ggo_print_help(&help, flags); - printf("supported audio formats: %s\n", AUDIO_FORMAT_HANDLERS); - exit(0); + if (OPT_GIVEN(DETAILED_HELP)) + help = lls_long_help(CMD_PTR); + else if (OPT_GIVEN(HELP)) + help = lls_short_help(CMD_PTR); + else + return; + printf("%s\n", help); + free(help); + exit(EXIT_SUCCESS); } static void parse_config_or_die(int argc, char *argv[]) { - int i, ret; - char *config_file; - struct play_cmdline_parser_params params = { - .override = 0, - .initialize = 1, - .check_required = 0, - .check_ambiguity = 0, - .print_errors = 1 - }; + const struct lls_command *cmd = CMD_PTR; + int i, ret, cf_argc; + char *cf, *errctx, **cf_argv; + struct lls_parse_result *cf_lpr, *merged_lpr; + unsigned num_kmas; + void *map; + size_t sz; - play_cmdline_parser_ext(argc, argv, &conf, ¶ms); - loglevel = get_loglevel_by_name(conf.loglevel_arg); - version_handle_flag("play", conf.version_given); - if (conf.help_given || conf.detailed_help_given) - print_help_and_die(); - if (conf.config_file_given) - config_file = para_strdup(conf.config_file_arg); + ret = lls(lls_parse(argc, argv, cmd, &play_lpr, &errctx)); + if (ret < 0) + goto fail; + loglevel = OPT_UINT32_VAL(LOGLEVEL); + version_handle_flag("play", OPT_GIVEN(VERSION)); + handle_help_flags(); + if (OPT_GIVEN(CONFIG_FILE)) + cf = para_strdup(OPT_STRING_VAL(CONFIG_FILE)); else { char *home = para_homedir(); - config_file = make_message("%s/.paraslash/play.conf", home); + cf = make_message("%s/.paraslash/play.conf", home); free(home); } - ret = file_exists(config_file); - if (conf.config_file_given && !ret) { - PARA_EMERG_LOG("can not read config file %s\n", config_file); - goto err; - } - if (ret) { - params.initialize = 0; - params.check_required = 1; - play_cmdline_parser_config_file(config_file, &conf, ¶ms); - loglevel = get_loglevel_by_name(conf.loglevel_arg); + ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL); + if (ret < 0) { + if (ret != -E_EMPTY && ret != -ERRNO_TO_PARA_ERROR(ENOENT)) + goto free_cf; + if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) && OPT_GIVEN(CONFIG_FILE)) + goto free_cf; + ret = 0; + goto free_cf; } - for (i = 0; i < conf.key_map_given; i++) { - char *kma = conf.key_map_arg[i]; + ret = lls(lls_convert_config(map, sz, NULL, &cf_argv, &errctx)); + para_munmap(map, sz); + if (ret < 0) + goto free_cf; + cf_argc = ret; + ret = lls(lls_parse(cf_argc, cf_argv, cmd, &cf_lpr, &errctx)); + lls_free_argv(cf_argv); + if (ret < 0) + goto free_cf; + ret = lls(lls_merge(play_lpr, cf_lpr, cmd, &merged_lpr, &errctx)); + lls_free_parse_result(cf_lpr, cmd); + if (ret < 0) + goto free_cf; + lls_free_parse_result(play_lpr, cmd); + play_lpr = merged_lpr; + loglevel = OPT_UINT32_VAL(LOGLEVEL); + + ret = lls(lls_check_arg_count(play_lpr, 1, INT_MAX, &errctx)); + if (ret < 0) + goto free_cf; + num_kmas = OPT_GIVEN(KEY_MAP); + for (i = 0; i < num_kmas; i++) { + const char *kma = lls_string_val(i, OPT_RESULT(KEY_MAP)); if (*kma && strchr(kma + 1, ':')) continue; PARA_EMERG_LOG("invalid key map arg: %s\n", kma); - goto err; + goto free_cf; } - free(config_file); - return; -err: - free(config_file); + ret = 1; +free_cf: + free(cf); + if (ret >= 0) + return; + lls_free_parse_result(play_lpr, cmd); +fail: + if (errctx) + PARA_EMERG_LOG("%s\n", errctx); + free(errctx); + PARA_EMERG_LOG("%s\n", para_strerror(-ret)); exit(EXIT_FAILURE); } @@ -292,10 +333,16 @@ static int shuffle_compare(__a_unused const void *a, __a_unused const void *b) return para_random(100) - 50; } -static void shuffle(char **base, size_t num) +static void init_shuffle_map(void) { + unsigned n, num_inputs = lls_num_inputs(play_lpr); + shuffle_map = para_malloc(num_inputs * sizeof(unsigned)); + for (n = 0; n < num_inputs; n++) + shuffle_map[n] = n; + if (!OPT_GIVEN(RANDOMIZE)) + return; srandom(time(NULL)); - qsort(base, num, sizeof(char *), shuffle_compare); + qsort(shuffle_map, num_inputs, sizeof(unsigned), shuffle_compare); } static struct btr_node *new_recv_btrn(struct receiver_node *rn) @@ -308,8 +355,9 @@ static struct btr_node *new_recv_btrn(struct receiver_node *rn) static int open_new_file(struct play_task *pt) { int ret; - char *tmp, *path = conf.inputs[pt->next_file], *errctx = NULL, - *argv[] = {"play", "-f", path, "-b", "0", NULL}; + const char *path = get_playlist_file(pt->next_file); + char *tmp = para_strdup(path), *errctx; + char *argv[] = {"play", "-f", tmp, "-b", "0", NULL}; PARA_NOTICE_LOG("next file: %s\n", path); wipe_receiver_node(pt); @@ -425,9 +473,10 @@ fail: static int next_valid_file(struct play_task *pt) { int i, j = pt->current_file; + unsigned num_inputs = lls_num_inputs(play_lpr); - FOR_EACH_PLAYLIST_FILE(i) { - j = (j + 1) % conf.inputs_num; + for (i = 0; i < num_inputs; i++) { + j = (j + 1) % num_inputs; if (!pt->invalid[j]) return j; } @@ -472,11 +521,12 @@ static void kill_stream(struct play_task *pt) static int previous_valid_file(struct play_task *pt) { int i, j = pt->current_file; + unsigned num_inputs = lls_num_inputs(play_lpr); - FOR_EACH_PLAYLIST_FILE(i) { + for (i = 0; i < num_inputs; i++) { j--; if (j < 0) - j = conf.inputs_num - 1; + j = num_inputs - 1; if (!pt->invalid[j]) return j; } @@ -527,7 +577,7 @@ static const char *default_keyseqs[] = {INTERNAL_KEYMAP_ENTRIES}; static const char *default_commands[] = {INTERNAL_KEYMAP_ENTRIES}; #undef KEYMAP_ENTRY #define NUM_INTERNALLY_MAPPED_KEYS ARRAY_SIZE(default_commands) -#define NUM_MAPPED_KEYS (NUM_INTERNALLY_MAPPED_KEYS + conf.key_map_given) +#define NUM_MAPPED_KEYS (NUM_INTERNALLY_MAPPED_KEYS + OPT_GIVEN(KEY_MAP)) #define FOR_EACH_MAPPED_KEY(i) for (i = 0; i < NUM_MAPPED_KEYS; i++) static inline bool is_internal_key(int key) @@ -558,9 +608,9 @@ static inline int get_key_map_idx(int key) get_internal_key_map_idx(key) : get_user_key_map_idx(key); } -static inline char *get_user_key_map_arg(int key) +static inline const char *get_user_key_map_arg(int key) { - return conf.key_map_arg[get_user_key_map_idx(key)]; + return lls_string_val(get_user_key_map_idx(key), OPT_RESULT(KEY_MAP)); } static inline char *get_internal_key_map_seq(int key) @@ -761,7 +811,7 @@ static int com_info(struct play_task *pt, static char dflt[] = "[no information available]"; sz = xasprintf(&buf, "playlist_pos: %u\npath: %s\n", - pt->current_file, conf.inputs[pt->current_file]); + pt->current_file, get_playlist_file(pt->current_file)); btr_add_output(buf, sz, pt->btrn); buf = pt->afhi_txt? pt->afhi_txt : dflt; btr_add_output_dont_free(buf, strlen(buf), pt->btrn); @@ -775,7 +825,7 @@ static void list_file(struct play_task *pt, int num) size_t sz; sz = xasprintf(&buf, "%s %4d %s\n", num == pt->current_file? - "*" : " ", num, conf.inputs[num]); + "*" : " ", num, get_playlist_file(num)); btr_add_output(buf, sz, pt->btrn); } @@ -799,8 +849,9 @@ static int com_ls(struct play_task *pt, __a_unused struct lls_parse_result *lpr) { int i; + unsigned num_inputs = lls_num_inputs(play_lpr); - FOR_EACH_PLAYLIST_FILE(i) + for (i = 0; i < num_inputs; i++) list_file(pt, i); return 0; } @@ -831,7 +882,7 @@ static int com_play(struct play_task *pt, struct lls_parse_result *lpr) ret = para_atoi32(lls_input(0, lpr), &x); if (ret < 0) return ret; - if (x < 0 || x >= conf.inputs_num) + if (x < 0 || x >= lls_num_inputs(play_lpr)) return -ERRNO_TO_PARA_ERROR(EINVAL); kill_stream(pt); pt->next_file = x; @@ -1057,8 +1108,8 @@ static void session_open(struct play_task *pt) struct sigaction act; PARA_NOTICE_LOG("\n%s\n", version_text("play")); - if (conf.history_file_given) - history_file = para_strdup(conf.history_file_arg); + if (OPT_GIVEN(HISTORY_FILE)) + history_file = para_strdup(OPT_STRING_VAL(HISTORY_FILE)); else { char *home = para_homedir(); history_file = make_message("%s/.paraslash/play.history", @@ -1201,7 +1252,7 @@ static unsigned get_time_string(struct play_task *pt, char **result) length? (seconds * 100 + length / 2) / length : 0, length / 60, length % 60, - conf.inputs[pt->current_file] + get_playlist_file(pt->current_file) ); } @@ -1258,6 +1309,7 @@ int main(int argc, char *argv[]) { int ret; struct play_task *pt = &play_task; + unsigned num_inputs; /* needed this early to make help work */ recv_init(); @@ -1265,17 +1317,14 @@ int main(int argc, char *argv[]) writer_init(); sched.default_timeout.tv_sec = 5; - parse_config_or_die(argc, argv); - if (conf.inputs_num == 0) - print_help_and_die(); AFH_RECV->init(); session_open(pt); - if (conf.randomize_given) - shuffle(conf.inputs, conf.inputs_num); - pt->invalid = para_calloc(sizeof(*pt->invalid) * conf.inputs_num); + num_inputs = lls_num_inputs(play_lpr); + init_shuffle_map(); + pt->invalid = para_calloc(sizeof(*pt->invalid) * num_inputs); pt->rq = CRT_FILE_CHANGE; - pt->current_file = conf.inputs_num - 1; + pt->current_file = num_inputs - 1; pt->playing = true; pt->task = task_register(&(struct task_info){ .name = "play", -- 2.39.5