]> git.tuebingen.mpg.de Git - paraslash.git/commitdiff
Merge branch 't/interactive'
authorAndre Noll <maan@systemlinux.org>
Sun, 4 Dec 2011 23:13:32 +0000 (00:13 +0100)
committerAndre Noll <maan@systemlinux.org>
Sun, 4 Dec 2011 23:22:32 +0000 (00:22 +0100)
Conflicts:
string.h

48 files changed:
.gitignore
Doxyfile
FEATURES
Makefile.in
NEWS
afs.c
audioc.c
audiod.c
audiod_command.c
bash_completion
client.c
client.h
client_common.c
command.c
command_util.sh
configure.ac
daemon.c
daemon.h
dccp_send.c
error.h
fade.c
filter.c
ggo/audioc.m4
ggo/client.m4
ggo/complete.m4 [new file with mode: 0644]
ggo/history_file.m4 [new file with mode: 0644]
ggo/makefile
grab_client.c
grab_client.h
gui.c
http_send.c
interactive.c [new file with mode: 0644]
interactive.h [new file with mode: 0644]
para.h
recv.c
sched.c
sched.h
send_common.c
server.c
string.c
string.h
udp_send.c
vss.c
vss.h
web/manual.m4
write.c
write_common.c
write_common.h

index 91c6f8589b600c26b00e845fa4413b85ba038944..d337367bfe0325eeb6caa6b6f6982eb35bdb14c2 100644 (file)
@@ -31,3 +31,4 @@ confdefs.h
 conftest
 conftest.c
 git-version.h
+*_completion.h
index 81e414d5609c8408aec577a0524f3e3f4d10afe4..83973cfb9dc23c6d6eaacb2efe7406c74d6b5d73 100644 (file)
--- a/Doxyfile
+++ b/Doxyfile
@@ -623,7 +623,8 @@ EXCLUDE_PATTERNS       = *.cmdline.* \
                          gcc-compat.h \
                          fade.c \
                          config.h \
-                       *_command_list.[ch]
+                       *_command_list.[ch] \
+                       *_completion.h
 
 # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
 # (namespaces, classes, functions, etc.) that should be excluded from the
index 613acc19c2f34a14d15a86921aaf94486ac04df2..6ad420dfe7a63800b3725f1295ec3c84cb580d5d 100644 (file)
--- a/FEATURES
+++ b/FEATURES
@@ -18,6 +18,7 @@ Features
        * Sophisticated audio file selector
        * Small memory footprint
        * Command line interface for easy scripting in high-level languages
+       * Interactive sessions offer command completion and command line history
        * RSA user authentication
        * Encrypted communications
        * GPL licensed
index 23ceda47a1db0cb2460c2656c4bcbd89cdbbd165..cf9598f71a0da39073d9dba6b8e85b9be5e4cbca 100644 (file)
@@ -109,6 +109,15 @@ tarball: $(tarball)
 
 -include $(ggo_dir)/makefile
 
+# When in doubt, use brute force (Ken Thompson)
+TOUPPER = \
+$(subst a,A,$(subst b,B,$(subst c,C,$(subst d,D,$(subst e,E,\
+$(subst f,F,$(subst g,G,$(subst h,H,$(subst i,I,$(subst j,J,\
+$(subst k,K,$(subst l,L,$(subst m,M,$(subst n,N,$(subst o,O,\
+$(subst p,P,$(subst q,Q,$(subst r,R,$(subst s,S,$(subst t,T,\
+$(subst u,U,$(subst v,V,$(subst w,W,$(subst x,X,$(subst y,Y,\
+$(subst z,Z,$1))))))))))))))))))))))))))
+
 %_command_list.c: %.cmd
        @[ -z "$(Q)" ] || echo 'GEN $@'
        $(Q) ./command_util.sh c < $< >$@
@@ -118,6 +127,10 @@ tarball: $(tarball)
 %_command_list.man: %.cmd
        @[ -z "$(Q)" ] || echo 'GEN $@'
        $(Q) ./command_util.sh man < $< > $@
+%_completion.h: %.cmd
+       @[ -z "$(Q)" ] || echo 'GEN $@'
+       $(Q) ./command_util.sh compl $(strip $(call TOUPPER,$(*F)))_COMPLETERS \
+               $(strip $(call TOUPPER,$(*F)))_COMMANDS < $< > $@
 
 server_command_lists_man = server_command_list.man afs_command_list.man
 man/man1/para_server.1: para_server $(server_command_lists_man) | $(man_dir)
@@ -283,8 +296,7 @@ clean:
 clean2: clean
        @[ -z "$(Q)" ] || echo 'CLEAN2'
        $(Q) rm -rf man $(object_dir) $(cmdline_dir)
-       $(Q) rm -f *_command_list.* $(ggo_generated)
-
+       $(Q) rm -f *_command_list.* *_completion.h $(ggo_generated)
 distclean: clean2 test-clean
        @[ -z "$(Q)" ] || echo 'DISTCLEAN'
        $(Q) rm -f Makefile autoscan.log config.status config.log
diff --git a/NEWS b/NEWS
index 8bb2862e93012c815d50be0513dee99c2b8eda87..1c6d821dbbc362ae77190724a37c2cc497d7fcd6 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -2,12 +2,16 @@
 0.4.9 (to be announced) "hybrid causality"
 ------------------------------------------
 
-Support for another audio format and many small improvements/fixes
-all over the place.
+Support for another audio format, interactive mode for para_client
+and para_audiod and many small improvements/fixes all over the place.
 
        - Support for flac, the free lossless audio codec.
        - Fix for an endless loop in the mp3 decoder for certain
          (corrupt) mp3 files.
+       - When executed without specifying a command, para_client
+         and para_audioc start an interactive shell (requires
+         libreadline being installed). The interactive mode offers
+         full tab completion and command line history.
        - autogen.sh now detects a distcc setup and adjusts the
          parameter for the -j option of make accordingly.
        - Shared memory areas are no longer restricted to 64K. We now
diff --git a/afs.c b/afs.c
index 78d8571fbaf1902b05054a3c15954795de79f858..ff3c95fe03853b0aaa80d873ea75748a10bf73d2 100644 (file)
--- a/afs.c
+++ b/afs.c
@@ -730,11 +730,11 @@ static void afs_signal_post_select(struct sched *s, struct task *t)
        }
        PARA_EMERG_LOG("terminating on signal %d\n", signum);
 shutdown:
-       sched_shutdown();
+       sched_shutdown(s);
        t->error = -E_AFS_SIGNAL;
 }
 
-static void register_signal_task(void)
+static void register_signal_task(struct sched *s)
 {
        struct signal_task *st = &signal_task_struct;
 
@@ -748,7 +748,7 @@ static void register_signal_task(void)
        st->task.pre_select = signal_pre_select;
        st->task.post_select = afs_signal_post_select;
        sprintf(st->task.status, "signal task");
-       register_task(&st->task);
+       register_task(s, &st->task);
 }
 
 static struct list_head afs_client_list;
@@ -909,7 +909,7 @@ static void command_post_select(struct sched *s, struct task *t)
        ret = execute_server_command(&s->rfds);
        if (ret < 0) {
                PARA_EMERG_LOG("%s\n", para_strerror(-ret));
-               sched_shutdown();
+               sched_shutdown(s);
                return;
        }
        /* Check the list of connected clients. */
@@ -944,7 +944,7 @@ static void command_post_select(struct sched *s, struct task *t)
        para_list_add(&client->node, &afs_client_list);
 }
 
-static void register_command_task(uint32_t cookie)
+static void register_command_task(uint32_t cookie, struct sched *s)
 {
        struct command_task *ct = &command_task_struct;
        ct->fd = setup_command_socket_or_die();
@@ -953,7 +953,7 @@ static void register_command_task(uint32_t cookie)
        ct->task.pre_select = command_pre_select;
        ct->task.post_select = command_post_select;
        sprintf(ct->task.status, "afs command task");
-       register_task(&ct->task);
+       register_task(s, &ct->task);
 }
 
 /**
@@ -967,7 +967,7 @@ __noreturn void afs_init(uint32_t cookie, int socket_fd)
        static struct sched s;
        int i, ret;
 
-       register_signal_task();
+       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]);
@@ -981,7 +981,7 @@ __noreturn void afs_init(uint32_t cookie, int socket_fd)
        PARA_INFO_LOG("server_socket: %d, afs_socket_cookie: %u\n",
                server_socket, (unsigned) cookie);
        init_admissible_files(conf.afs_initial_mode_arg);
-       register_command_task(cookie);
+       register_command_task(cookie, &s);
        s.default_timeout.tv_sec = 0;
        s.default_timeout.tv_usec = 999 * 1000;
        ret = schedule(&s);
index 6b1a64f978c9dadf38cd6cf46c4070e74f384510..e12d6e98fce4baa902d03e21c48aee4fba0237da 100644 (file)
--- a/audioc.c
+++ b/audioc.c
@@ -8,6 +8,8 @@
 
 #include <regex.h>
 #include <sys/types.h>
+#include <stdbool.h>
+#include <signal.h>
 
 #include "audioc.cmdline.h"
 #include "para.h"
@@ -21,6 +23,8 @@ INIT_AUDIOC_ERRLISTS;
 
 /** The gengetopt structure containing command line args. */
 static struct audioc_args_info conf;
+static char *socket_name;
+
 
 static int loglevel;
 INIT_STDERR_LOGGING(loglevel);
@@ -38,6 +42,215 @@ static char *concat_args(unsigned argc, char * const *argv)
        return buf;
 }
 
+#ifdef HAVE_READLINE
+#include "list.h"
+#include "sched.h"
+#include "buffer_tree.h"
+#include "interactive.h"
+#include "audiod_completion.h"
+
+static struct sched sched;
+
+struct audioc_task {
+       int fd;
+       struct btr_node *btrn;
+       struct task task;
+};
+
+static struct i9e_completer audiod_completers[];
+
+I9E_DUMMY_COMPLETER(cycle);
+I9E_DUMMY_COMPLETER(off);
+I9E_DUMMY_COMPLETER(on);
+I9E_DUMMY_COMPLETER(sb);
+I9E_DUMMY_COMPLETER(tasks);
+I9E_DUMMY_COMPLETER(term);
+
+static void help_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *result)
+{
+       result->matches = i9e_complete_commands(ci->word, audiod_completers);
+}
+
+static void stat_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+{
+       char *sia[] = {STATUS_ITEM_ARRAY NULL};
+       char *opts[] = {"-p", NULL};
+
+       if (ci->word_num <= 2 && ci->word && ci->word[0] == '-')
+               i9e_complete_option(opts, ci, cr);
+       else
+               i9e_extract_completions(ci->word, sia, &cr->matches);
+}
+
+static void grab_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+{
+       char *opts[] = {"-ms", "-ms", "-ma", "-p=", "-n=", "-o", NULL};
+       i9e_complete_option(opts, ci, cr);
+}
+
+static struct i9e_completer audiod_completers[] = {
+       AUDIOD_COMPLETERS
+       {.name = NULL}
+};
+
+static void audioc_pre_select(struct sched *s, struct task *t)
+{
+       struct audioc_task *at = container_of(t, struct audioc_task, task);
+       int ret = btr_node_status(at->btrn, 0, BTR_NT_ROOT);
+
+       if (ret < 0)
+               sched_min_delay(s);
+       para_fd_set(at->fd, &s->rfds, &s->max_fileno);
+}
+
+static void audioc_post_select(struct sched *s, struct task *t)
+{
+       char *buf = NULL;
+       struct audioc_task *at = container_of(t, struct audioc_task, task);
+       int ret = btr_node_status(at->btrn, 0, BTR_NT_ROOT);
+
+       if (ret < 0)
+               goto out;
+       if (!FD_ISSET(at->fd, &s->rfds))
+               return;
+       buf = para_malloc(conf.bufsize_arg);
+       ret = recv_bin_buffer(at->fd, buf, conf.bufsize_arg);
+       PARA_DEBUG_LOG("recv: %d\n", ret);
+       if (ret == 0)
+               ret = -E_AUDIOC_EOF;
+       if (ret < 0)
+               goto out;
+       btr_add_output(buf, ret, at->btrn);
+       return;
+out:
+       if (ret < 0) {
+               free(buf);
+               btr_remove_node(at->btrn);
+               btr_free_node(at->btrn);
+               at->btrn = NULL;
+               close(at->fd);
+       }
+       t->error = ret;
+}
+
+static struct audioc_task audioc_task = {
+       .task = {
+               .pre_select = audioc_pre_select,
+               .post_select = audioc_post_select,
+               .status = "audioc task"
+       },
+}, *at = &audioc_task;
+
+static int audioc_i9e_line_handler(char *line)
+{
+       char *args = NULL;
+       int ret;
+       if (!line || !*line)
+               return 0;
+
+       PARA_DEBUG_LOG("line: %s\n", line);
+       ret = create_argv(line, " ", &conf.inputs);
+       if (ret < 0)
+               return ret;
+       conf.inputs_num = ret;
+       args = concat_args(conf.inputs_num, conf.inputs);
+       free_argv(conf.inputs);
+       conf.inputs_num = 0; /* required for audioc_cmdline_parser_free() */
+       ret = connect_local_socket(socket_name);
+       if (ret < 0)
+               goto out;
+       at->fd = ret;
+       ret = mark_fd_nonblocking(at->fd);
+       if (ret < 0)
+               goto close;
+       ret = send_cred_buffer(at->fd, args);
+       if (ret < 0)
+               goto close;
+       free(args);
+       args = NULL;
+       at->btrn = btr_new_node(&(struct btr_node_description)
+               EMBRACE(.name = "audioc line handler"));
+       at->task.error = 0;
+       register_task(&sched, &at->task);
+       i9e_attach_to_stdout(at->btrn);
+       return 1;
+close:
+       close(at->fd);
+out:
+       free(args);
+       return ret;
+}
+
+__noreturn static void interactive_session(void)
+{
+       int ret;
+       char *history_file;
+       struct sigaction act;
+       struct i9e_client_info ici = {
+               .fds = {0, 1, 2},
+               .prompt = "para_audioc> ",
+               .line_handler = audioc_i9e_line_handler,
+               .loglevel = loglevel,
+               .completers = audiod_completers,
+       };
+       PARA_NOTICE_LOG("\n%s\n", VERSION_TEXT("audioc"));
+       if (conf.history_file_given)
+               history_file = para_strdup(conf.history_file_arg);
+       else {
+               char *home = para_homedir();
+               history_file = make_message("%s/.paraslash/audioc.history",
+                       home);
+               free(home);
+       }
+       ici.history_file = history_file;
+
+       act.sa_handler = i9e_signal_dispatch;
+       sigemptyset(&act.sa_mask);
+       act.sa_flags = 0;
+       sigaction(SIGINT, &act, NULL);
+       sched.select_function = i9e_select;
+
+       sched.default_timeout.tv_sec = 1;
+       ret = i9e_open(&ici, &sched);
+       if (ret < 0)
+               goto out;
+       para_log = i9e_log;
+       ret = schedule(&sched);
+       i9e_close();
+       para_log = stderr_log;
+out:
+       free(history_file);
+       audioc_cmdline_parser_free(&conf);
+       if (ret < 0)
+               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+       exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+__noreturn static void print_completions(void)
+{
+       int ret = i9e_print_completions(audiod_completers);
+       exit(ret <= 0? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+#else /* HAVE_READLINE */
+
+__noreturn static void interactive_session(void)
+{
+       PARA_EMERG_LOG("interactive sessions not available\n");
+       exit(EXIT_FAILURE);
+}
+
+__noreturn static void print_completions(void)
+{
+       PARA_EMERG_LOG("command completion not available\n");
+       exit(EXIT_FAILURE);
+}
+
+#endif /* HAVE_READLINE */
+
 static char *configfile_exists(void)
 {
        static char *config_file;
@@ -91,19 +304,24 @@ int main(int argc, char *argv[])
                }
        }
        loglevel = get_loglevel_by_name(conf.loglevel_arg);
-       args = conf.inputs_num?
-               concat_args(conf.inputs_num, conf.inputs) :
-               para_strdup("stat");
-
        if (conf.socket_given)
-               ret = connect_local_socket(conf.socket_arg);
+               socket_name = para_strdup(conf.socket_arg);
        else {
-               char *hn = para_hostname(), *socket_name = make_message(
-                       "/var/paraslash/audiod_socket.%s", hn);
-               ret = connect_local_socket(socket_name);
+               char *hn = para_hostname();
+               socket_name = make_message("/var/paraslash/audiod_socket.%s",
+                       hn);
                free(hn);
-               free(socket_name);
        }
+
+       if (conf.complete_given)
+               print_completions();
+
+       if (conf.inputs_num == 0)
+               interactive_session();
+       args = concat_args(conf.inputs_num, conf.inputs);
+
+       ret = connect_local_socket(socket_name);
+       free(socket_name);
        if (ret < 0) {
                PARA_EMERG_LOG("failed to connect to local socket\n");
                goto out;
index 8008e2cb6459f84d3b258bd9efeb0fffc77e4016..295787262ae66589ceb6dc2d56da258e77943ef8 100644 (file)
--- a/audiod.c
+++ b/audiod.c
@@ -33,6 +33,7 @@
 #include "signal.h"
 #include "version.h"
 
+__printf_2_3 void (*para_log)(int, const char*, ...) = daemon_log;
 /** define the array of error lists needed by para_audiod */
 INIT_AUDIOD_ERRLISTS;
 /** define the array containing all supported audio formats */
@@ -77,6 +78,8 @@ enum vss_status_flags {
        VSS_STATUS_FLAG_PLAYING = 2,
 };
 
+struct sched sched = {.max_fileno = 0};
+
 /**
  * The task for obtaining para_server's status (para_client stat).
  *
@@ -504,7 +507,7 @@ static void open_filters(struct slot_info *s)
                                .handler = f->execute, .context = fn));
 
                f->open(fn);
-               register_task(&fn->task);
+               register_task(&sched, &fn->task);
                parent = fn->btrn;
                PARA_NOTICE_LOG("%s filter %d/%d (%s) started in slot %d\n",
                        audio_formats[s->format], i,  nf, f->name, (int)(s - slot));
@@ -527,7 +530,7 @@ static void open_writers(struct slot_info *s)
                wn = s->wns + i;
                wn->conf = a->writer_conf[i];
                wn->writer_num = a->writer_nums[i];
-               register_writer_node(wn, parent);
+               register_writer_node(wn, parent, &sched);
        }
 }
 
@@ -564,7 +567,7 @@ static int open_receiver(int format)
        rn->task.pre_select = r->pre_select;
        rn->task.post_select = r->post_select;
        sprintf(rn->task.status, "%s receiver node", r->name);
-       register_task(&rn->task);
+       register_task(&sched, &rn->task);
        return slot_num;
 }
 
@@ -1146,7 +1149,7 @@ static void start_stop_decoders(void)
        if (a->num_filters)
                open_filters(sl);
        open_writers(sl);
-       activate_grab_clients();
+       activate_grab_clients(&sched);
        btr_log_tree(sl->receiver_node->btrn, LL_NOTICE);
 }
 
@@ -1179,7 +1182,7 @@ min_delay:
 }
 
 /* restart the client task if necessary */
-static void status_post_select(__a_unused struct sched *s, struct task *t)
+static void status_post_select(struct sched *s, struct task *t)
 {
        struct status_task *st = container_of(t, struct status_task, task);
 
@@ -1241,13 +1244,13 @@ static void status_post_select(__a_unused struct sched *s, struct task *t)
                int argc = 5;
                PARA_INFO_LOG("clock diff count: %d\n", st->clock_diff_count);
                st->clock_diff_count--;
-               client_open(argc, argv, &st->ct, NULL, NULL, st->btrn);
+               client_open(argc, argv, &st->ct, NULL, NULL, st->btrn, s);
                set_stat_task_restart_barrier(2);
 
        } else {
                char *argv[] = {"audiod", "--", "stat", "-p", NULL};
                int argc = 4;
-               client_open(argc, argv, &st->ct, NULL, NULL, st->btrn);
+               client_open(argc, argv, &st->ct, NULL, NULL, st->btrn, s);
                set_stat_task_restart_barrier(5);
        }
        free(stat_item_values[SI_BASENAME]);
@@ -1331,7 +1334,6 @@ static void init_colors_or_die(void)
 int main(int argc, char *argv[])
 {
        int ret, i;
-       static struct sched s;
        struct command_task command_task_struct, *cmd_task = &command_task_struct;
        struct audiod_cmdline_parser_params params = {
                .override = 0,
@@ -1383,12 +1385,12 @@ int main(int argc, char *argv[])
        if (conf.daemon_given)
                daemonize(false /* parent exits immediately */);
 
-       register_task(&sig_task->task);
-       register_task(&cmd_task->task);
-       register_task(&stat_task->task);
-       s.default_timeout.tv_sec = 2;
-       s.default_timeout.tv_usec = 999 * 1000;
-       ret = schedule(&s);
+       register_task(&sched, &sig_task->task);
+       register_task(&sched, &cmd_task->task);
+       register_task(&sched, &stat_task->task);
+       sched.default_timeout.tv_sec = 2;
+       sched.default_timeout.tv_usec = 999 * 1000;
+       ret = schedule(&sched);
 
        PARA_EMERG_LOG("%s\n", para_strerror(-ret));
        return EXIT_FAILURE;
index dfb014aebc1c1d12659364189ac79813c952139c..adfa07edfc6c603b936cb52df4615323b5d62bb0 100644 (file)
@@ -27,6 +27,7 @@
 #include "fd.h"
 #include "audiod_command_list.h"
 
+extern struct sched sched;
 extern char *stat_item_values[NUM_STAT_ITEMS];
 
 /** Iterate over the array of all audiod commands. */
@@ -286,7 +287,7 @@ out:
 
 int com_tasks(int fd, __a_unused int argc, __a_unused char **argv)
 {
-       char *tl = get_task_list();
+       char *tl = get_task_list(&sched);
        int ret = 1;
        if (tl)
                ret = client_write(fd, tl);
@@ -344,7 +345,7 @@ int com_stat(int fd, int argc, char **argv)
 
 int com_grab(int fd, int argc, char **argv)
 {
-       return grab_client_new(fd, argc, argv);
+       return grab_client_new(fd, argc, argv, &sched);
 }
 
 __noreturn int com_term(int fd, __a_unused int argc, __a_unused char **argv)
index a9947b74edfe12d79cb6f619142e0da4a67a0253..51bbc68e0d6b39dded6889d69d6da8078c930634 100644 (file)
-# Copyright (C) 2007 Andre Noll <maan@systemlinux.org>
+# Copyright (C) 2007-2011 Andre Noll <maan@systemlinux.org>
 #
 # Licensed under the GPL v2. For licencing details see COPYING.
 
-PC="para_client -l error -- "
-
-__para_commandlist=
-__para_sender_list=
-__para_table_list=
-__para_mood_list=
-__para_lyrics_list=
-__para_image_list=
-__para_playlist_list=
-__para_attributes_list=
-
-__paracomp()
-{
-        local cur="${COMP_WORDS[COMP_CWORD]}"
-        if [ $# -gt 2 ]; then
-                cur="$3"
-        fi
-        COMPREPLY=($(compgen -P "$2" -W "$1" -- "$cur"))
-}
-
-__para_complete_file()
-{
-       local cur="${COMP_WORDS[COMP_CWORD]}"
-       local sed_cmd="-e s|^\($cur[^/]\+/\).*|\1|1"
-       $PC ls -p -sp "${cur}*" | sed $sed_cmd | uniq
-}
-
-__para_complete_command()
-{
-       if test -z "$__para_command_list"; then
-               __para_command_list="$($PC help | cut -f 1)"
-       fi
-       echo "$__para_command_list"
-}
-
-__para_complete_table()
-{
-       if test -z "$__para_table_list"; then
-               __para_table_list="$(ls $HOME/.paraslash/afs_database-0.4/)"
-       fi
-       echo "$__para_table_list"
-}
-__para_complete_sender()
-{
-       if test -z "$__para_sender_list"; then
-               __para_sender_list="$($PC si | grep '^.* sender:$' | sed -e 's/ sender://')"
-       fi
-       echo "$__para_sender_list"
-}
-__para_complete_attribute()
-{
-       if test -z "$__para_attributes_list"; then
-               __para_attributes_list="$($PC lsatt)"
-       fi
-       echo "$__para_attributes_list"
-}
-__para_complete_playlist()
-{
-       if test -z "$__para_playlist_list"; then
-               __para_playlist_list="$($PC lspl)"
-       fi
-       echo "$__para_playlist_list"
-}
-__para_complete_mood()
-{
-       if test -z "$__para_mood_list"; then
-               __para_mood_list="$($PC lsmood)"
-       fi
-       echo "$__para_mood_list"
-}
-__para_complete_image()
+_para_complete()
 {
-       if test -z "$__para_image_list"; then
-               __para_image_list="$($PC lsimg)"
+       local prg="$1" # the program to execute
+       local cur=${COMP_WORDS[$COMP_CWORD]}
+       local line="$COMP_LINE" OLD_IFS="$IFS"
+       local opts n
+
+       if [[ "$COMP_WORDBREAKS" != ' '  ]]; then
+               COMP_WORDBREAKS=' '
+               return 124 # try again with proper value
        fi
-       echo "$__para_imagelist"
-}
-__para_complete_lyrics()
-{
-       if test -z "$__para_lyrics_list"; then
-               __para_lyrics_list="$($PC lslyr)"
+       # This extracts short and long options from the help output
+       local script='{
+               if ($1 ~ "-[a-zA-Z]," && $2 ~ "--[a-zA-Z]") {
+                       print substr($1, 0, 2);
+                       gsub("=.*", "", $2)
+                       print $2
+               } else if ($1 ~ "--[a-zA-Z]") {
+                       gsub("=.*", "", $1)
+                       print $1
+               }
+       }'
+
+       if [[ "$cur" == -* ]]; then # option
+               # Depending on whether '--' is one of the previous words we
+               # complete either on local options, i.e. those of the program
+               # to execute, or call the program to print possible completions
+               # (to a subcommand).
+               local_opts=true
+               for ((i=0; i < $COMP_CWORD; i++)); do
+                       [[ "${COMP_WORDS[$i]}" != '--' ]] && continue
+                       local_opts=false
+                       break
+               done
+               if [[ "$local_opts" == "true" ]]; then
+                       result="-- $($prg --help | awk "$script")"
+                       COMPREPLY=($(compgen -W "$result" -- $cur))
+                       return
+               fi
        fi
-       echo "$__para_lyrics_list"
-}
-
-__para_select()
-{
-       local cur="${COMP_WORDS[COMP_CWORD]}"
-       case "$cur" in
-       *)
-               __paracomp "$($PC lspl "${cur}*" | sed -e 's|^|p/|1') $($PC lsmood | sed -e 's|^|m/|1')"
-               ;;
-       esac
-}
-
-__para_setatt()
-{
-       local cur="${COMP_WORDS[COMP_CWORD]}"
-       if [ $COMP_CWORD -lt 3 ]; then
-               __paracomp "$(__para_complete_attribute)"
+       # We need to call the program with --complete to get the possible
+       # completions. Before that, all local options must be discarded.
+       IFS=' '
+       n=0
+       for word in $line; do
+               ((n > 0)) && ! [[ "$word" == -* ]] && break
+               line="${line##*( )}" # remove leading whitespace
+               line="${line##+([^ ])}"
+               line="${line##*( )}"
+               let n++
+               [[ "$word" == '--' ]] && break
+       done
+       IFS="$OLD_IFS"
+       s=$((${#COMP_LINE} - ${#line})) # how many characters have been cut
+       if (($COMP_POINT > $s)); then
+               COMP_POINT=$(($COMP_POINT - $s))
        else
-               if test -z "$cur" -o "$cur" = "${cur#/}"; then
-                       __paracomp "$(__para_complete_attribute)"
-               else
-                       __paracomp "$(__para_complete_file)"
-               fi
+               COMP_POINT=0
        fi
+       COMP_LINE="$line"
+       #echo "line: $COMP_LINE, point: $COMP_POINT"
+       export COMP_LINE COMP_POINT
+       result=($($prg --complete))
+
+       # the last line of the output contains the options for compopt,
+       # prefixed with '-o='.
+       n=${#result[@]}
+       (($n == 0)) && return # oops, $prg did not write any output
+       let n--
+       opts="${result[$n]}"
+       result[$n]=
+       opts="${opts#-o=}"
+       IFS=','
+       compopt +o nospace
+       for opt in $opts; do
+               #echo "opt: $opt"
+               case "$opt" in
+               filenames) compopt -o filenames;;
+               nospace) compopt -o nospace;;
+               esac
+       done
+       IFS="$OLD_IFS"
+       COMPREPLY=(${result[@]})
 }
 
-__para_sender()
+_para_audioc()
 {
-       if test $COMP_CWORD -eq 2; then
-               __paracomp "$(__para_complete_sender)"
-       elif test $COMP_CWORD -eq 3; then
-               __paracomp "on off add delete allow deny help"
-       else
-               COMPREPLY=()
-       fi
+       _para_complete ~maan/para/para_audioc
 }
+complete -F _para_audioc para_audioc
 
 _para_client()
 {
-       local i c=1 command
-
-
-       while [ $c -lt $COMP_CWORD ]; do
-               i="${COMP_WORDS[c]}"
-               case "$i" in
-               --*) ;;
-               *) command="$i"; break ;;
-               esac
-               c=$((++c))
-       done
-
-       if [ $c -eq $COMP_CWORD ]; then
-               case "${COMP_WORDS[COMP_CWORD]}" in
-               --*=*) COMPREPLY=();;
-               *) __paracomp "$(__para_complete_command)";;
-               esac
-               return
-       fi
-
-       case "$command" in
-       stop|play|term|hup|pause|nomore|si|version) COMPREPLY=();;
-       setatt) __para_setatt;;
-       select) __para_select;;
-       touch|ls|rm|cpsi) __paracomp "$(__para_complete_file)";;
-       mvatt|lsatt|rmatt) __paracomp "$(__para_complete_attribute)";;
-       help) __paracomp "$(__para_complete_command)";;
-       sender) __para_sender;;
-       init) __paracomp __paracomp "$(__para_complete_table)";;
-       mvmood|lsmood|rmmood) __paracomp "$(__para_complete_mood)";;
-       mvlyr|lslyr|rmlyr) __paracomp "$(__para_complete_lyrics)";;
-       mvimg|lsimg|rmimg) __paracomp "$(__para_complete_image)";;
-       mvpl|lspl|rmpl) __paracomp "$(__para_complete_playlist)";;
-       esac
-
+       _para_complete ~maan/para/para_client
 }
 complete -o default -o nospace -F _para_client para_client
 complete -o default -o nospace -F _para_client para
-
index b6a2f24157b7a01a63f9bbb13418c2c2326559dc..c6fde49000b3e0844414c979bf6cc5541cff4eed 100644 (file)
--- a/client.c
+++ b/client.c
@@ -8,6 +8,7 @@
 
 #include <regex.h>
 #include <stdbool.h>
+#include <signal.h>
 
 #include "para.h"
 #include "list.h"
 #include "client.h"
 #include "buffer_tree.h"
 #include "error.h"
+#include "version.h"
 
 INIT_CLIENT_ERRLISTS;
 
+static struct sched sched;
 static struct client_task *ct;
 static struct stdin_task sit;
 static struct stdout_task sot;
 
-static void supervisor_post_select(__a_unused struct sched *s, struct task *t)
+static int client_loglevel = LL_ERROR;
+DEFINE_STDERR_LOGGER(stderr_log, client_loglevel);
+__printf_2_3 void (*para_log)(int, const char*, ...) = stderr_log;
+
+#ifdef HAVE_READLINE
+#include "interactive.h"
+#include "server_completion.h"
+#include "afs_completion.h"
+
+struct exec_task {
+       struct task task;
+       struct btr_node *btrn;
+       char *result_buf;
+       size_t result_size;
+};
+
+static void exec_pre_select(struct sched *s, struct task *t)
+{
+       struct exec_task *et = container_of(t, struct exec_task, task);
+       int ret = btr_node_status(et->btrn, 0, BTR_NT_LEAF);
+
+       if (ret != 0)
+               sched_min_delay(s);
+}
+
+static void exec_post_select(__a_unused struct sched *s, struct task *t)
+{
+       struct exec_task *et = container_of(t, struct exec_task, task);
+       struct btr_node *btrn = et->btrn;
+       char *buf;
+       size_t sz;
+       int ret;
+
+       ret = btr_node_status(btrn, 0, BTR_NT_LEAF);
+       if (ret <= 0) {
+               t->error = ret;
+               return;
+       }
+       sz = btr_next_buffer(btrn, &buf);
+       if (sz <= 1)
+               goto out;
+       et->result_buf = para_realloc(et->result_buf, et->result_size + sz - 1);
+       memcpy(et->result_buf + et->result_size - 1, buf, sz -  1);
+       et->result_size += sz - 1;
+       et->result_buf[et->result_size - 1] = '\0';
+out:
+       btr_consume(btrn, sz);
+}
+
+static int make_client_argv(const char *line)
+{
+       int ret;
+
+       free_argv(ct->conf.inputs);
+       ret = create_argv(line, " ", &ct->conf.inputs);
+       if (ret >= 0)
+               ct->conf.inputs_num = ret;
+       return ret;
+}
+
+static int execute_client_command(const char *cmd, char **result)
+{
+       int ret;
+       struct sched command_sched = {.default_timeout = {.tv_sec = 1}};
+       struct exec_task exec_task = {
+               .task = {
+                       .pre_select = exec_pre_select,
+                       .post_select = exec_post_select,
+                       .status = "client exec task",
+               },
+               .result_buf = para_strdup(""),
+               .result_size = 1,
+       };
+       *result = NULL;
+       ret = make_client_argv(cmd);
+       if (ret < 0)
+               goto out;
+       exec_task.btrn = btr_new_node(&(struct btr_node_description)
+               EMBRACE(.name = "exec_collect"));
+       register_task(&command_sched, &exec_task.task);
+       ret = client_connect(ct, &command_sched, NULL, exec_task.btrn);
+       if (ret < 0)
+               goto out;
+       schedule(&command_sched);
+       *result = exec_task.result_buf;
+       btr_remove_node(exec_task.btrn);
+       client_disconnect(ct);
+       ret = 1;
+out:
+       btr_free_node(exec_task.btrn);
+       if (ret < 0)
+               free(exec_task.result_buf);
+       return ret;
+}
+
+static int extract_matches_from_command(const char *word, char *cmd,
+               char ***matches)
+{
+       char *buf, **sl;
+       int ret;
+
+       ret = execute_client_command(cmd, &buf);
+       if (ret < 0)
+               return ret;
+       ret = create_argv(buf, "\n", &sl);
+       free(buf);
+       if (ret < 0)
+               return ret;
+       ret = i9e_extract_completions(word, sl, matches);
+       free_argv(sl);
+       return ret;
+}
+
+static int complete_attributes(const char *word, char ***matches)
+{
+       return extract_matches_from_command(word, "lsatt", matches);
+}
+
+static void complete_addblob(__a_unused const char *blob_type,
+               __a_unused struct i9e_completion_info *ci,
+               __a_unused struct i9e_completion_result *cr)
+{
+       cr->filename_completion_desired = true;
+}
+
+static void generic_blob_complete(const char *blob_type,
+               struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+{
+       char cmd[20];
+       sprintf(cmd, "ls%s", blob_type);
+       extract_matches_from_command(ci->word, cmd, &cr->matches);
+}
+
+static void complete_catblob(const char *blob_type,
+               struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+{
+       generic_blob_complete(blob_type, ci, cr);
+}
+
+static void complete_lsblob(const char *blob_type,
+               struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+{
+       char *opts[] = {"-i", "-l", "-r", NULL};
+
+       if (ci->word[0] == '-')
+               return i9e_complete_option(opts, ci, cr);
+       generic_blob_complete(blob_type, ci, cr);
+}
+
+static void complete_rmblob(const char *blob_type,
+               struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+{
+       generic_blob_complete(blob_type, ci, cr);
+}
+
+static void complete_mvblob(const char *blob_type,
+               struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+{
+       generic_blob_complete(blob_type, ci, cr);
+}
+
+/* these don't need any completions */
+I9E_DUMMY_COMPLETER(ff);
+I9E_DUMMY_COMPLETER(hup);
+I9E_DUMMY_COMPLETER(jmp);
+I9E_DUMMY_COMPLETER(next);
+I9E_DUMMY_COMPLETER(nomore);
+I9E_DUMMY_COMPLETER(pause);
+I9E_DUMMY_COMPLETER(play);
+I9E_DUMMY_COMPLETER(si);
+I9E_DUMMY_COMPLETER(term);
+I9E_DUMMY_COMPLETER(version);
+I9E_DUMMY_COMPLETER(stop);
+I9E_DUMMY_COMPLETER(addatt);
+I9E_DUMMY_COMPLETER(init);
+
+static struct i9e_completer completers[];
+
+static void help_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *result)
+{
+       result->matches = i9e_complete_commands(ci->word, completers);
+}
+
+static void stat_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+{
+       char *opts[] = {"-n=", "-p", NULL};
+       //PARA_CRIT_LOG("word: %s\n", ci->word);
+       i9e_complete_option(opts, ci, cr);
+}
+
+static void sender_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+{
+       char *senders[] = {"http", "dccp", "udp", NULL};
+       char *http_cmds[] = {"on", "off", "allow", "deny", "help", NULL};
+       char *dccp_cmds[] = {"on", "off", "allow", "deny", "help", NULL};
+       char *udp_cmds[] ={"on", "off", "add", "delete", "help", NULL};
+       char *sender;
+       char **cmds;
+
+       //PARA_CRIT_LOG("wn: %d\n", ci->word_num);
+       if (ci->word_num == 0 || ci->word_num > 3)
+               return;
+       if (ci->word_num == 1 || (ci->word_num == 2 && *ci->word != '\0')) {
+               i9e_extract_completions(ci->word, senders, &cr->matches);
+               return;
+       }
+       sender = ci->argv[1];
+       //PARA_CRIT_LOG("sender: %s\n", sender);
+       if (strcmp(sender, "http") == 0)
+               cmds = http_cmds;
+       else if (strcmp(sender, "dccp") == 0)
+               cmds = dccp_cmds;
+       else if (strcmp(sender, "udp") == 0)
+               cmds = udp_cmds;
+       else
+               return;
+       i9e_extract_completions(ci->word, cmds, &cr->matches);
+}
+
+static void add_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+{
+       char *opts[] = {"-a", "-l", "-f", "-v", "--", NULL};
+
+       if (ci->word[0] == '-')
+               i9e_complete_option(opts, ci, cr);
+       cr->filename_completion_desired = true;
+}
+
+static void ls_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+{
+       char *opts[] = {
+               "--", "-l", "-ls", "-ll", "-lv", "-lp", "-lm", "-lc", "-p",
+               "-a", "-r", "-d", "-sp", "-sl", "-ss", "-sn", "-sf", "-sc",
+               "-si", "-sy", "-sb", "-sd", "-sa", NULL
+       };
+       if (ci->word[0] == '-')
+               i9e_complete_option(opts, ci, cr);
+       cr->filename_completion_desired = true;
+}
+
+static void setatt_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+{
+       char *buf, **sl;
+       int i, ret, num_atts;
+
+       if (ci->word_num == 0)
+               return;
+
+       if (*ci->word == '/' || *ci->word == '\0')
+               cr->filename_completion_desired = true;
+       if (*ci->word == '/')
+               return;
+       ret = execute_client_command("lsatt", &buf);
+       if (ret < 0)
+               return;
+       ret = create_argv(buf, "\n", &sl);
+       if (ret < 0)
+               goto out;
+       num_atts = ret;
+       sl = para_realloc(sl, (2 * num_atts + 1) * sizeof(char *));
+       for (i = 0; i < num_atts; i++) {
+               char *orig = sl[i];
+               sl[i] = make_message("%s+", orig);
+               sl[num_atts + i] = make_message("%s-", orig);
+               free(orig);
+       }
+       sl[2 * num_atts] = NULL;
+       ret = i9e_extract_completions(ci->word, sl, &cr->matches);
+out:
+       free(buf);
+       free_argv(sl);
+}
+
+static void lsatt_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+{
+       char *opts[] = {"-i", "-l", "-r", NULL};
+
+       if (ci->word[0] == '-')
+               i9e_complete_option(opts, ci, cr);
+       else
+               complete_attributes(ci->word, &cr->matches);
+}
+
+static void mvatt_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+{
+       complete_attributes(ci->word, &cr->matches);
+}
+
+static void rmatt_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+{
+       complete_attributes(ci->word, &cr->matches);
+}
+
+static void check_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+{
+       char *opts[] = {"-a", "-m", "-p", NULL};
+       i9e_complete_option(opts, ci, cr);
+}
+
+static void rm_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+{
+       char *opts[] = {"-v", "-f", "-p", NULL};
+
+       if (ci->word[0] == '-') {
+               i9e_complete_option(opts, ci, cr);
+               return;
+       }
+       cr->filename_completion_desired = true;
+}
+
+static void touch_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+{
+       char *opts[] = {"-n=", "-l=", "-y=", "-i=", "-a=", "-v", "-p", NULL};
+
+       if (ci->word[0] == '-')
+               i9e_complete_option(opts, ci, cr);
+       cr->filename_completion_desired = true;
+}
+
+static void cpsi_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+{
+       char *opts[] = {"-a", "-y", "-i", "-l", "-n", "-v", NULL};
+
+       if (ci->word[0] == '-')
+               i9e_complete_option(opts, ci, cr);
+       cr->filename_completion_desired = true;
+}
+
+static void select_completer(struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+{
+       char *mood_buf, *pl_buf, **moods, **playlists, **mops;
+       int num_moods, num_pl, i, n, ret;
+
+       ret = execute_client_command("lsmood", &mood_buf);
+       if (ret < 0)
+               return;
+       ret = execute_client_command("lspl", &pl_buf);
+       if (ret < 0)
+               goto free_mood_buf;
+
+       ret = create_argv(mood_buf, "\n", &moods);
+       if (ret < 0)
+               goto free_pl_buf;
+       num_moods = ret;
+       ret = create_argv(pl_buf, "\n", &playlists);
+       if (ret < 0)
+               goto free_moods;
+       num_pl = ret;
+       n = num_moods + num_pl;
+       mops = para_malloc((n + 1) * sizeof(char *));
+       for (i = 0; i < num_moods; i++)
+               mops[i] = make_message("m/%s", moods[i]);
+       for (i = 0; i < num_pl; i++)
+               mops[num_moods + i] = make_message("p/%s", playlists[i]);
+       mops[n] = NULL;
+       i9e_extract_completions(ci->word, mops, &cr->matches);
+       free_argv(mops);
+       free_argv(playlists);
+free_moods:
+       free_argv(moods);
+free_pl_buf:
+       free(pl_buf);
+free_mood_buf:
+       free(mood_buf);
+}
+
+#define DEFINE_BLOB_COMPLETER(cmd, blob_type) \
+       static void cmd ## blob_type ## _completer( \
+               struct i9e_completion_info *ci, \
+               struct i9e_completion_result *cr) \
+       {complete_ ## cmd ## blob(#blob_type, ci, cr);}
+
+DEFINE_BLOB_COMPLETER(add, mood)
+DEFINE_BLOB_COMPLETER(add, lyr)
+DEFINE_BLOB_COMPLETER(add, img)
+DEFINE_BLOB_COMPLETER(add, pl)
+DEFINE_BLOB_COMPLETER(cat, mood)
+DEFINE_BLOB_COMPLETER(cat, lyr)
+DEFINE_BLOB_COMPLETER(cat, img)
+DEFINE_BLOB_COMPLETER(cat, pl)
+DEFINE_BLOB_COMPLETER(ls, mood)
+DEFINE_BLOB_COMPLETER(ls, lyr)
+DEFINE_BLOB_COMPLETER(ls, img)
+DEFINE_BLOB_COMPLETER(ls, pl)
+DEFINE_BLOB_COMPLETER(rm, mood)
+DEFINE_BLOB_COMPLETER(rm, lyr)
+DEFINE_BLOB_COMPLETER(rm, img)
+DEFINE_BLOB_COMPLETER(rm, pl)
+DEFINE_BLOB_COMPLETER(mv, mood)
+DEFINE_BLOB_COMPLETER(mv, lyr)
+DEFINE_BLOB_COMPLETER(mv, img)
+DEFINE_BLOB_COMPLETER(mv, pl)
+
+static int client_i9e_line_handler(char *line)
+{
+       int ret;
+
+       client_disconnect(ct);
+       if (!line || !*line)
+               return 0;
+       PARA_DEBUG_LOG("line handler: %s\n", line);
+       ret = make_client_argv(line);
+       if (ret < 0)
+               return ret;
+       ret = client_connect(ct, &sched, NULL, NULL);
+       if (ret < 0)
+               return ret;
+       i9e_attach_to_stdout(ct->btrn);
+       return 1;
+}
+
+static void client_sighandler(int s)
+{
+       i9e_signal_dispatch(s);
+}
+
+static struct i9e_completer completers[] = {
+       SERVER_COMPLETERS
+       AFS_COMPLETERS
+       {.name = NULL}
+};
+
+__noreturn static void interactive_session(void)
+{
+       int ret;
+       char *history_file;
+       struct sigaction act;
+       struct i9e_client_info ici = {
+               .fds = {0, 1, 2},
+               .prompt = "para_client> ",
+               .line_handler = client_i9e_line_handler,
+               .loglevel = client_loglevel,
+               .completers = completers,
+       };
+
+       PARA_NOTICE_LOG("\n%s\n", VERSION_TEXT("client"));
+       if (ct->conf.history_file_given)
+               history_file = para_strdup(ct->conf.history_file_arg);
+       else {
+               char *home = para_homedir();
+               history_file = make_message("%s/.paraslash/client.history",
+                       home);
+               free(home);
+       }
+       ici.history_file = history_file;
+
+       act.sa_handler = client_sighandler;
+       sigemptyset(&act.sa_mask);
+       act.sa_flags = 0;
+       sigaction(SIGINT, &act, NULL);
+       sched.select_function = i9e_select;
+
+       ret = i9e_open(&ici, &sched);
+       if (ret < 0)
+               goto out;
+       para_log = i9e_log;
+       ret = schedule(&sched);
+       i9e_close();
+       para_log = stderr_log;
+out:
+       if (ret < 0)
+               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+       exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS);
+       //client_close(ct);
+}
+
+__noreturn static void print_completions(void)
+{
+       int ret = i9e_print_completions(completers);
+       exit(ret <= 0? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+#else /* HAVE_READLINE */
+
+__noreturn static void interactive_session(void)
+{
+       PARA_EMERG_LOG("interactive sessions not available\n");
+       exit(EXIT_FAILURE);
+}
+
+__noreturn static void print_completions(void)
+{
+       PARA_EMERG_LOG("command completion not available\n");
+       exit(EXIT_FAILURE);
+}
+
+#endif /* HAVE_READLINE */
+
+static void supervisor_post_select(struct sched *s, struct task *t)
 {
        if (ct->task.error < 0) {
                t->error = ct->task.error;
@@ -35,15 +545,14 @@ static void supervisor_post_select(__a_unused struct sched *s, struct task *t)
        }
        if (ct->status == CL_SENDING) {
                stdin_set_defaults(&sit);
-               register_task(&sit.task);
+               register_task(s, &sit.task);
                t->error = -E_TASK_STARTED;
                return;
        }
        if (ct->status == CL_RECEIVING) {
                stdout_set_defaults(&sot);
-               register_task(&sot.task);
-               t->error = -E_TASK_STARTED;
-               return;
+               register_task(s, &sot.task);
+               t->error = -E_TASK_STARTED; return;
        }
 }
 
@@ -52,18 +561,16 @@ static struct task svt = {
        .status = "supervisor task"
 };
 
-static int client_loglevel = LL_ERROR; /* loglevel */
-INIT_STDERR_LOGGING(client_loglevel);
-
 /**
  * The client program to connect to para_server.
  *
  * \param argc Usual argument count.
  * \param argv Usual argument vector.
  *
- * It registers two tasks: The client task that communicates with para_server
- * and the supervisor task that minitors whether the client task intends to
- * read from stdin or write to stdout.
+ * When called without a paraslash command, an interactive session is started.
+ * Otherwise, the client task and the supervisor task are started. The former
+ * communicates with para_server while the latter monitors whether the client
+ * task intends to read from stdin or write to stdout.
  *
  * Once it has been determined whether the client command corresponds to a
  * stdin command (addmood, addimg, ..), either the stdin task or the stdout
@@ -75,13 +582,19 @@ INIT_STDERR_LOGGING(client_loglevel);
  */
 int main(int argc, char *argv[])
 {
-
        int ret;
-       static struct sched s;
 
        init_random_seed_or_die();
-       s.default_timeout.tv_sec = 1;
-       s.default_timeout.tv_usec = 0;
+       sched.default_timeout.tv_sec = 1;
+
+       ret = client_parse_config(argc, argv, &ct, &client_loglevel);
+       if (ret < 0)
+               goto out;
+       if (ct->conf.complete_given)
+               print_completions();
+       if (ret == 0)
+               interactive_session(); /* does not return */
+
        /*
         * We add buffer tree nodes for stdin and stdout even though
         * only one of them will be needed. This simplifies the code
@@ -89,13 +602,13 @@ int main(int argc, char *argv[])
         */
        sit.btrn = btr_new_node(&(struct btr_node_description)
                EMBRACE(.name = "stdin"));
-       ret = client_open(argc, argv, &ct, &client_loglevel, sit.btrn, NULL);
+       ret = client_open(argc, argv, &ct, &client_loglevel, sit.btrn, NULL, &sched);
        if (ret < 0)
                goto out;
        sot.btrn = btr_new_node(&(struct btr_node_description)
                EMBRACE(.name = "stdout", .parent = ct->btrn));
-       register_task(&svt);
-       ret = schedule(&s);
+       register_task(&sched, &svt);
+       ret = schedule(&sched);
 out:
        client_close(ct);
        btr_free_node(sit.btrn);
index 28b786a4ce091e132863a0076c6473b645dd60d2..f2d9ecb14c28041bbd7fbbd1dabd3d9a5472ca19 100644 (file)
--- a/client.h
+++ b/client.h
@@ -46,6 +46,12 @@ struct client_task {
        struct btr_node *btrn;
 };
 
+void client_disconnect(struct client_task *ct);
 void client_close(struct client_task *ct);
+int client_parse_config(int argc, char *argv[], struct client_task **ct_ptr,
+               int *loglevel);
+int client_connect(struct client_task *ct, struct sched *s,
+               struct btr_node *parent, struct btr_node *child);
 int client_open(int argc, char *argv[], struct client_task **ct_ptr,
-               int *loglevel, struct btr_node *parent, struct btr_node *child);
+               int *loglevel, struct btr_node *parent, struct btr_node *child,
+               struct sched *sched);
index eb9f9e1fcda2a49ba960582ba67be9c882897a20..8c8d6622c801fac58a43a4a2047155e8fc0949bf 100644 (file)
 #define CLIENT_BUFSIZE 4000
 
 /**
- * Close the connection to para_server and free all resources.
+ * Close the connection to para_server and deallocate per-command ressources.
  *
- * \param ct Pointer to the client data.
+ * \param ct The client task.
  *
- * \sa client_open.
+ * This frees all ressources of the current command but keeps the configuration
+ * in \p ct->conf.
+ *
+ * \sa \ref client_close().
  */
-void client_close(struct client_task *ct)
+void client_disconnect(struct client_task *ct)
 {
        if (!ct)
                return;
        if (ct->scc.fd >= 0)
                close(ct->scc.fd);
        sc_free(ct->scc.recv);
+       ct->scc.recv = NULL;
        sc_free(ct->scc.send);
+       ct->scc.send = NULL;
+       btr_free_node(ct->btrn);
+       ct->btrn = NULL;
+}
+
+/**
+ * Close the connection to para_server and free all resources.
+ *
+ * \param ct Pointer to the client data.
+ *
+ * \sa \ref client_open(), \ref client_disconnect().
+ */
+void client_close(struct client_task *ct)
+{
+       if (!ct)
+               return;
+       client_disconnect(ct);
        free(ct->user);
        free(ct->config_file);
        free(ct->key_file);
-       btr_free_node(ct->btrn);
        client_cmdline_parser_free(&ct->conf);
        free(ct);
 }
@@ -309,25 +329,43 @@ out:
        }
 }
 
-/* connect to para_server and register the client task */
-static int client_connect(struct client_task *ct)
+/**
+ * Connect to para_server and register the client task.
+ *
+ * \param ct The initialized client task structure.
+ * \param s The scheduler instance to register the client task to.
+ * \param parent The parent node of the client btr node.
+ * \param child The child node of the client node.
+ *
+ * The client task structure given by \a ct  must be allocated and initialized
+ * by \ref client_parse_config() before this function is called.
+ *
+ * \return Standard.
+ */
+int client_connect(struct client_task *ct, struct sched *s,
+               struct btr_node *parent, struct btr_node *child)
 {
        int ret;
 
+       PARA_NOTICE_LOG("connecting %s:%d\n", ct->conf.hostname_arg,
+               ct->conf.server_port_arg);
        ct->scc.fd = -1;
        ret = para_connect_simple(IPPROTO_TCP, ct->conf.hostname_arg,
                                               ct->conf.server_port_arg);
        if (ret < 0)
                return ret;
        ct->scc.fd = ret;
-       ct->status = CL_CONNECTED;
        ret = mark_fd_nonblocking(ct->scc.fd);
        if (ret < 0)
                goto err_out;
+       ct->status = CL_CONNECTED;
+       ct->btrn = btr_new_node(&(struct btr_node_description)
+               EMBRACE(.name = "client", .parent = parent, .child = child));
        ct->task.pre_select = client_pre_select;
        ct->task.post_select = client_post_select;
+       ct->task.error = 0;
        sprintf(ct->task.status, "client");
-       register_task(&ct->task);
+       register_task(s, &ct->task);
        return 1;
 err_out:
        close(ct->scc.fd);
@@ -336,40 +374,36 @@ err_out:
 }
 
 /**
- * Open connection to para_server.
+ * Parse a client configuration.
  *
  * \param argc Usual argument count.
  * \param argv Usual argument vector.
- * \param ct_ptr Points to dynamically allocated and initialized client task
- * struct upon successful return.
+ * \param ct_ptr Filled in by this function.
  * \param loglevel If not \p NULL, the number of the loglevel is stored here.
- * \param parent Add the new buffer tree node as a child of this node.
- * \param child Add the new buffer tree node as a parent of this node.
  *
- * Check the command line options given by \a argc and argv, set default values
- * for user name and rsa key file, read further option from the config file.
- * Finally, establish a connection to para_server.
+ * This checks the command line options given by \a argc and \a argv, sets
+ * default values for the user name and the name of the rsa key file and reads
+ * further options from the config file.
  *
- * \return Standard.
+ * Upon successful return, \a ct_ptr points to a dynamically allocated and
+ * initialized client task struct.
+ *
+ * \return The number of non-option arguments in \a argc/argv on success,
+ * negative on errors.
  */
-int client_open(int argc, char *argv[], struct client_task **ct_ptr,
-               int *loglevel, struct btr_node *parent, struct btr_node *child)
+int client_parse_config(int argc, char *argv[], struct client_task **ct_ptr,
+               int *loglevel)
 {
        char *home = para_homedir();
        int ret;
        struct client_task *ct = para_calloc(sizeof(struct client_task));
 
-       ct->btrn = btr_new_node(&(struct btr_node_description)
-               EMBRACE(.name = "client", .parent = parent, .child = child));
        *ct_ptr = ct;
        ct->scc.fd = -1;
        ret = -E_CLIENT_SYNTAX;
        if (client_cmdline_parser(argc, argv, &ct->conf))
                goto out;
        HANDLE_VERSION_FLAG("client", ct->conf);
-       ret = -E_CLIENT_SYNTAX;
-       if (!ct->conf.inputs_num)
-               goto out;
 
        ct->config_file = ct->conf.config_file_given?
                para_strdup(ct->conf.config_file_arg) :
@@ -411,16 +445,52 @@ int client_open(int argc, char *argv[], struct client_task **ct_ptr,
        PARA_INFO_LOG("loglevel: %s\n", ct->conf.loglevel_arg);
        PARA_INFO_LOG("config_file: %s\n", ct->config_file);
        PARA_INFO_LOG("key_file: %s\n", ct->key_file);
-       PARA_NOTICE_LOG("connecting %s:%d\n", ct->conf.hostname_arg,
-               ct->conf.server_port_arg);
-       ret = client_connect(ct);
+       ret = ct->conf.inputs_num;
 out:
        free(home);
        if (ret < 0) {
                PARA_ERROR_LOG("%s\n", para_strerror(-ret));
-               btr_remove_node(ct->btrn);
                client_close(ct);
                *ct_ptr = NULL;
        }
        return ret;
 }
+
+/**
+ * Parse the client configuration and open a connection to para_server.
+ *
+ * \param argc See \ref client_parse_config.
+ * \param argv See \ref client_parse_config.
+ * \param ct_ptr See \ref client_parse_config.
+ * \param loglevel See \ref client_parse_config.
+ * \param parent See \ref client_connect().
+ * \param child See \ref client_connect().
+ * \param sched See \ref client_connect().
+ *
+ * This function combines client_parse_config() and client_connect(). It is
+ * considered a syntax error if no command was given, i.e. if the number
+ * of non-option arguments is zero.
+ *
+ * \return Standard.
+ */
+int client_open(int argc, char *argv[], struct client_task **ct_ptr,
+               int *loglevel, struct btr_node *parent, struct btr_node *child,
+               struct sched *sched)
+{
+       int ret = client_parse_config(argc, argv, ct_ptr, loglevel);
+
+       if (ret < 0)
+               return ret;
+       if (ret == 0) {
+               ret = -E_CLIENT_SYNTAX;
+               goto fail;
+       }
+       ret = client_connect(*ct_ptr, sched, parent, child);
+       if (ret < 0)
+               goto fail;
+       return 1;
+fail:
+       client_close(*ct_ptr);
+       *ct_ptr = NULL;
+       return ret;
+}
index b6861b88bbcb36621109f6d9ac7fe80546a42b35..932c9f3a2c7ef51014c9630514513ee427f0280b 100644 (file)
--- a/command.c
+++ b/command.c
@@ -24,6 +24,7 @@
 #include "server.h"
 #include "list.h"
 #include "send.h"
+#include "sched.h"
 #include "vss.h"
 #include "net.h"
 #include "daemon.h"
@@ -32,7 +33,6 @@
 #include "user_list.h"
 #include "server_command_list.h"
 #include "afs_command_list.h"
-#include "sched.h"
 #include "signal.h"
 #include "version.h"
 
index 8f82b39318103dd73b44315ddaa3548912320bbc..76b3445fc1557b607ef72ae9408dec38f8a54507 100755 (executable)
@@ -236,6 +236,10 @@ dump_array_member()
        echo '},'
 }
 
+dump_completion()
+{
+       echo "  {.name = \"$name_txt\", .completer = ${name_txt}_completer}, \\"
+}
 
 template_loop()
 {
@@ -249,11 +253,11 @@ template_loop()
        for member in $template_members; do
                local sed_cmd="sed -e s/@member@/$member/g"
                #echo "sed_cmd: $sed_cmd"
-               name_txt=$(echo $t_name | $sed_cmd)
+               name_txt=$(echo "$t_name" | $sed_cmd)
                #name_txt="$tname"
-               perms_txt=$(echo $t_perms | $sed_cmd)
-               desc_txt=$(echo $t_desc | $sed_cmd)
-               usage_txt=$(echo $t_usage | $sed_cmd)
+               perms_txt=$(echo "$t_perms" | $sed_cmd)
+               desc_txt=$(echo "$t_desc" | $sed_cmd)
+               usage_txt=$(echo "$t_usage" | $sed_cmd)
                help_txt=$(printf "%s\n" "$t_help" | $sed_cmd)
                prototype=$(echo "$template_prototype" | $sed_cmd)
                $1
@@ -305,6 +309,27 @@ com_header()
        done
 }
 
+com_completion()
+{
+
+       echo "#define $1 \\"
+       while : ; do
+               read_one_command
+               if test $ret -lt 0; then
+                       exit 1
+               fi
+               if test $ret -eq 0; then
+                       break
+               fi
+               if test $template -eq 0; then
+                       dump_completion
+                       continue
+               fi
+               template_loop dump_completion
+       done
+       echo
+}
+
 read_header
 arg="$1"
 shift
@@ -318,4 +343,7 @@ case "$arg" in
        "man")
                com_man $*
                ;;
+       "compl")
+               com_completion $*
+               ;;
 esac
index a2e5fed14569deb9c059172d76b83dce0f10daa1..89242d969bbab440a8ac085262b3de45ea3d3392 100644 (file)
@@ -905,7 +905,69 @@ fi
 CPPFLAGS="$OLD_CPPFLAGS"
 LDFLAGS="$OLD_LDFLAGS"
 LIBS="$OLD_LIBS"
+############################################################# readline
+OLD_CPPFLAGS="$CPPFLAGS"
+OLD_LD_FLAGS="$LDFLAGS"
+OLD_LIBS="$LIBS"
+
+have_readline="yes"
+AC_ARG_WITH(readline_headers, [AC_HELP_STRING(--with-readline-headers=dir,
+       [look for libreadline header files also in dir])])
+if test -n "$with_readline_headers"; then
+       readline_cppflags="-I$with_readline_headers"
+       CPPFLAGS="$CPPFLAGS $readline_cppflags"
+fi
 
+AC_ARG_WITH(readline_libs, [AC_HELP_STRING(--with-readline-libs=dir,
+       [look for readline library also in dir])])
+if test -n "$with_readline_libs"; then
+       readline_libs="-L$with_readline_libs"
+       LDFLAGS="$LDFLAGS $readline_libs"
+fi
+msg="no interactive cli support"
+AC_CHECK_HEADERS([readline/readline.h], [
+       ], [
+       have_readline="no"
+       AC_MSG_WARN([readline/readline.h not found, $msg])
+])
+if test "$have_readline" = "yes"; then
+       readline_libs="$readline_libs -lreadline"
+       AC_SEARCH_LIBS([rl_replace_line], [readline], [], [have_readline="no"])
+       if test "$have_readline" = "no"; then # try with -lcurses
+                # clear cache
+               AC_MSG_NOTICE([trying again with -lcurses])
+                unset ac_cv_search_rl_replace_line 2> /dev/null
+               AC_SEARCH_LIBS([rl_replace_line], [readline], [
+                       have_readline=yes
+                       readline_libs="$readline_libs -lcurses"
+               ], [], [-lcurses])
+       fi
+       if test "$have_readline" = "no"; then # try with -ltermcap
+                # clear cache
+               AC_MSG_NOTICE([trying again with -ltermcap])
+                unset ac_cv_search_rl_replace_line 2> /dev/null
+               AC_SEARCH_LIBS([rl_replace_line], [readline], [
+                       have_readline=yes
+                       readline_libs="$readline_libs -ltermcap"
+               ], [], [-ltermcap])
+       fi
+fi
+
+if test "$have_readline" = "yes"; then
+       all_errlist_objs="$all_errlist_objs interactive"
+       client_errlist_objs="$client_errlist_objs interactive"
+       client_ldflags="$client_ldflags $readline_libs"
+       audioc_errlist_objs="$audioc_errlist_objs buffer_tree interactive sched time"
+       audioc_ldflags="$audioc_ldflags $readline_libs"
+       AC_SUBST(readline_cppflags)
+       AC_DEFINE(HAVE_READLINE, 1, define to 1 to turn on readline support)
+else
+       AC_MSG_WARN([libreadline not found or unusable])
+fi
+CPPFLAGS="$OLD_CPPFLAGS"
+LDFLAGS="$OLD_LDFLAGS"
+LIBS="$OLD_LIBS"
+#############################################################
 
 
 AC_SUBST(install_sh, [$INSTALL])
@@ -1048,6 +1110,7 @@ AC_MSG_NOTICE([
 paraslash configuration:
 ~~~~~~~~~~~~~~~~~~~~~~~~
 unix socket credentials: $have_ucred
+readline (interactive CLIs): $have_readline
 audio formats supported by para_server/para_afh: $server_audio_formats
 id3 version2 support: $have_libid3tag
 filters supported by para_audiod/para_filter: $filters
index 0bf2f0ac9e81d72dff8623d7ebc54014007c40e4..f06cf2f5a12d57015f3b66982a9aaab4f7e96622 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -357,7 +357,7 @@ __malloc char *get_server_uptime_str(const struct timeval *current_time)
  * \param ll The log level.
  * \param fmt The format string describing the log message.
  */
-__printf_2_3 void para_log(int ll, const char* fmt,...)
+__printf_2_3 void daemon_log(int ll, const char* fmt,...)
 {
        va_list argp;
        FILE *fp;
index 3fe72ea9149a56af3a9b8fdfe9c9f5b493962f94..fd435577e6258aa24fe2fe1edc9bde2ce50c7d5f 100644 (file)
--- a/daemon.h
+++ b/daemon.h
@@ -16,6 +16,7 @@ void daemon_clear_flag(unsigned flag);
 void daemon_set_loglevel(char *loglevel);
 void daemon_set_default_log_colors(void);
 void daemon_set_log_color_or_die(char const *arg);
+__printf_2_3 void daemon_log(int ll, const char* fmt,...);
 
 /** Daemon log configuration flags. */
 enum daemon_flags {
index 0484778eb593d342eaa0fd961a521e587a5ef468..53fa54e3fbac6fca71ea6005c10a6fa2e707dd3f 100644 (file)
@@ -24,6 +24,7 @@
 #include "net.h"
 #include "list.h"
 #include "send.h"
+#include "sched.h"
 #include "vss.h"
 #include "fd.h"
 #include "close_on_fork.h"
diff --git a/error.h b/error.h
index 311b6ebdab1c4248acefd0c08d1c5346948ec996..ae94157f81690568fc20a741c0f1757ef664e83a 100644 (file)
--- a/error.h
+++ b/error.h
@@ -248,6 +248,7 @@ extern const char **para_errlist[];
 
 #define AUDIOC_ERRORS \
        PARA_ERROR(AUDIOC_SYNTAX, "audioc syntax error"), \
+       PARA_ERROR(AUDIOC_EOF, "audioc: end of file"), \
 
 
 #define CLIENT_COMMON_ERRORS \
@@ -459,6 +460,10 @@ extern const char **para_errlist[];
        PARA_ERROR(QUEUE, "packet queue overrun"), \
 
 
+#define INTERACTIVE_ERRORS \
+       PARA_ERROR(I9E_EOF, "end of input"), \
+       PARA_ERROR(I9E_SETUPTERM, "failed to set up terminal"), \
+
 /** \endcond errors */
 
 /**
diff --git a/fade.c b/fade.c
index 04ca923770ec069bda49d3a069fac795fd461ac1..1aced9df5ad732aa4208dcbd87105b555425303b 100644 (file)
--- a/fade.c
+++ b/fade.c
@@ -27,7 +27,7 @@
 INIT_FADE_ERRLISTS;
 static struct fade_args_info conf;
 
-__printf_2_3 void para_log(__a_unused int ll, const char *fmt, ...)
+__printf_2_3 void date_log(__a_unused int ll, const char *fmt, ...)
 {
        va_list argp;
        time_t t1;
@@ -40,6 +40,7 @@ __printf_2_3 void para_log(__a_unused int ll, const char *fmt, ...)
        vprintf(fmt, argp);
        va_end(argp);
 }
+__printf_2_3 void (*para_log)(int, const char*, ...) = date_log;
 
 /*
  * Open the mixer device.
index 0dfc94b1cc7213e024f6bc626c48be79cb09df75..bc9952ae95d9c08d3df61a2bbc8e3ecd4329487f 100644 (file)
--- a/filter.c
+++ b/filter.c
@@ -120,7 +120,7 @@ int main(int argc, char *argv[])
        sit->btrn = btr_new_node(&(struct btr_node_description)
                EMBRACE(.name = "stdin"));
        stdin_set_defaults(sit);
-       register_task(&sit->task);
+       register_task(&s, &sit->task);
 
        fns = para_malloc(conf.filter_given * sizeof(*fns));
        for (i = 0, parent = sit->btrn; i < conf.filter_given; i++) {
@@ -143,13 +143,13 @@ int main(int argc, char *argv[])
                fn->task.pre_select = f->pre_select;
                fn->task.post_select = f->post_select;
                f->open(fn);
-               register_task(&fn->task);
+               register_task(&s, &fn->task);
                parent = fn->btrn;
        }
        sot->btrn = btr_new_node(&(struct btr_node_description)
                EMBRACE(.name = "stdout", .parent = parent));
        stdout_set_defaults(sot);
-       register_task(&sot->task);
+       register_task(&s, &sot->task);
 
        s.default_timeout.tv_sec = 1;
        s.default_timeout.tv_usec = 0;
index ad112e27dbe395cc055ebe4b99c36a59c8deca70..5ecc2a92b82b16d90056c361c059c9422946dbc3 100644 (file)
@@ -1,5 +1,5 @@
 include(header.m4)
-include(loglevel.m4)
+<qu>
 option "socket" s
 #~~~~~~~~~~~~~~~~
 "well-known socket (default=/var/paraslash/audiod.socket.$HOSTNAME)"
@@ -13,3 +13,10 @@ option "bufsize" b
        int typestr="bytes"
        default="8192"
        optional
+</qu>
+
+define(CURRENT_PROGRAM,para_audioc)
+define(DEFAULT_HISTORY_FILE,~/.paraslash/audioc.history)
+include(loglevel.m4)
+include(history_file.m4)
+include(complete.m4)
index 5770ff0a6711230f644699df0504d775c552eea6..8ade508985e6012a0b67414f3792a213caca7945 100644 (file)
@@ -1,6 +1,7 @@
 include(header.m4)
 define(CURRENT_PROGRAM,para_client)
 define(DEFAULT_CONFIG_FILE,~/.paraslash/client.conf)
+define(DEFAULT_HISTORY_FILE,~/.paraslash/client.history)
 <qu>
 args "--no-handle-error"
 option "hostname" i "ip or host to connect" string typestr="host" default="localhost" optional
@@ -11,3 +12,5 @@ option "key_file" k "(default='~/.paraslash/key.<user>')" string typestr="filena
 
 include(loglevel.m4)
 include(config_file.m4)
+include(history_file.m4)
+include(complete.m4)
diff --git a/ggo/complete.m4 b/ggo/complete.m4
new file mode 100644 (file)
index 0000000..14e737c
--- /dev/null
@@ -0,0 +1,12 @@
+<qu>
+option "complete" -
+#~~~~~~~~~~~~~~~~~~
+"print possible command line completions"
+       flag off
+       details = "
+       If this flag is given, </qu>CURRENT_PROGRAM<qu> reads the environment
+       variables COMP_LINE and COMP_POINT to obtain the current command line
+       and the cursor position respectively, prints possible completions
+       to stdout and exits.
+"
+</qu>
diff --git a/ggo/history_file.m4 b/ggo/history_file.m4
new file mode 100644 (file)
index 0000000..932d88b
--- /dev/null
@@ -0,0 +1,12 @@
+<qu>
+option "history_file" -
+#~~~~~~~~~~~~~~~~~~~~~~
+"(default='</qu>DEFAULT_HISTORY_FILE<qu>')"
+string typestr = "filename"
+optional
+details = "
+       If </qu>CURRENT_PROGRAM<qu> runs in interactive mode, it reads the history
+       file on startup. Upon exit, the in-memory history is appended
+       to the history file.
+"
+</qu>
index bcc172b204855ca037a04bf4bfd9269f581eab69..48fff2e4ad765e0046d79ba5c8023c92673f99cb 100644 (file)
@@ -54,13 +54,13 @@ $(ggo_dir)/server.ggo $(ggo_dir)/audiod.ggo: \
        $(ggo_dir)/group.m4 $(ggo_dir)/log_timing.m4
 
 $(ggo_dir)/afh.ggo: $(ggo_dir)/loglevel.m4
-$(ggo_dir)/audioc.ggo: $(ggo_dir)/loglevel.m4
+$(ggo_dir)/audioc.ggo: $(ggo_dir)/loglevel.m4 $(ggo_dir)/history_file.m4 $(ggo_dir)/complete.m4
 $(ggo_dir)/filter.ggo: $(ggo_dir)/loglevel.m4
 $(ggo_dir)/fsck.ggo: $(ggo_dir)/loglevel.m4
 $(ggo_dir)/gui.ggo: $(ggo_dir)/loglevel.m4
 $(ggo_dir)/recv.ggo: $(ggo_dir)/loglevel.m4
 $(ggo_dir)/write.ggo: $(ggo_dir)/loglevel.m4
-$(ggo_dir)/client.ggo: $(ggo_dir)/loglevel.m4 $(ggo_dir)/config_file.m4
+$(ggo_dir)/client.ggo: $(ggo_dir)/loglevel.m4 $(ggo_dir)/config_file.m4 $(ggo_dir)/history_file.m4 $(ggo_dir)/complete.m4
 
 $(ggo_dir)/%.ggo: $(ggo_dir)/%.m4 $(ggo_dir)/header.m4
        @[ -z "$(Q)" ] || echo 'M4 $<'
index 9109c2f8f6c5f1978eddd356ff7f8e979d73dd8a..e5f42416f57e87256d3e8e77eb94fa26c384046a 100644 (file)
@@ -116,7 +116,7 @@ static void gc_post_select(struct sched *s, struct task *t);
  *
  * \param gc The grab client to activate.
  */
-static void gc_activate(struct grab_client *gc)
+static void gc_activate(struct grab_client *gc, struct sched *s)
 {
        struct btr_node *root = audiod_get_btr_root(), *parent;
        char *name = gc->name? gc->name : "grab";
@@ -135,7 +135,7 @@ static void gc_activate(struct grab_client *gc)
        snprintf(gc->task.status, sizeof(gc->task.status) - 1, "%s", name);
        gc->task.status[sizeof(gc->task.status) - 1] = '\0';
        gc->task.error = 0;
-       register_task(&gc->task);
+       register_task(s, &gc->task);
 }
 
 /**
@@ -149,7 +149,7 @@ static void gc_activate(struct grab_client *gc)
  * This function also garbage collects all grab clients whose tasks have been
  * unscheduled.
  */
-void activate_grab_clients(void)
+void activate_grab_clients(struct sched *s)
 {
        struct grab_client *gc, *tmp;
 
@@ -159,7 +159,7 @@ void activate_grab_clients(void)
                        free(gc);
                        continue;
                }
-               gc_activate(gc);
+               gc_activate(gc, s);
        }
 }
 
@@ -266,6 +266,7 @@ static int gc_check_args(int argc, char **argv, struct grab_client *gc)
  * \param fd The file descriptor of the client.
  * \param argc Argument count.
  * \param argv Argument vector.
+ * \param s The scheduler to register the grab client task to.
  *
  * If the command line options given by \a argc and \a argv are valid.
  * allocate a struct grab_client and initialize it with this valid
@@ -276,7 +277,7 @@ static int gc_check_args(int argc, char **argv, struct grab_client *gc)
  *
  * \return Standard.
  */
-int grab_client_new(int fd, int argc, char **argv)
+int grab_client_new(int fd, int argc, char **argv, struct sched *s)
 {
        int ret;
        struct grab_client *gc = para_calloc(sizeof(struct grab_client));
@@ -286,7 +287,7 @@ int grab_client_new(int fd, int argc, char **argv)
                goto err_out;
        gc->fd = fd;
        para_list_add(&gc->node, &inactive_grab_client_list);
-       gc_activate(gc);
+       gc_activate(gc, s);
        return 1;
 err_out:
        free(gc);
index 88b954837f93f67e5965c4829e0fcb4d0e9856d0..2465d88bc877652fc1ef15cb4ec95a5543b8f342 100644 (file)
@@ -6,5 +6,5 @@
 
 /** \file grab_client.h exported symbols from grab_client.c */
 
-int grab_client_new(int fd, int argc, char **argv);
-void activate_grab_clients(void);
+int grab_client_new(int fd, int argc, char **argv, struct sched *s);
+void activate_grab_clients(struct sched *s);
diff --git a/gui.c b/gui.c
index a3c07ec796bd9d179ef633b3d81d91c14b42c834..3fc81965233ca2983cb7ae3c33238a6544ef87c9 100644 (file)
--- a/gui.c
+++ b/gui.c
@@ -489,7 +489,7 @@ static int add_output_line(char *line, __a_unused void *data)
 
 static int loglevel;
 
-__printf_2_3 void para_log(int ll, const char *fmt,...)
+__printf_2_3 void curses_log(int ll, const char *fmt,...)
 {
        int color;
        char *msg;
@@ -510,6 +510,7 @@ __printf_2_3 void para_log(int ll, const char *fmt,...)
        rb_add_entry(color, msg);
        wrefresh(bot.win);
 }
+__printf_2_3 void (*para_log)(int, const char*, ...) = curses_log;
 
 static void setup_signal_handling(void)
 {
index 8ae2341a0351d9edc447f544c33fd66e0fb6c74a..50211701b2f3df2c5fd8e919b352b7c967961b3a 100644 (file)
@@ -21,6 +21,7 @@
 #include "http.h"
 #include "list.h"
 #include "send.h"
+#include "sched.h"
 #include "vss.h"
 #include "close_on_fork.h"
 #include "net.h"
diff --git a/interactive.c b/interactive.c
new file mode 100644 (file)
index 0000000..3204775
--- /dev/null
@@ -0,0 +1,640 @@
+/*
+ * Copyright (C) 2011 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file interactive.c Readline abstraction for interactive sessions. */
+
+#include <regex.h>
+#include <stdbool.h>
+#include <curses.h>
+#include <readline/readline.h>
+#include <readline/history.h>
+#include <sys/ioctl.h>
+#include <assert.h>
+#include <signal.h>
+
+#include "para.h"
+#include "fd.h"
+#include "buffer_tree.h"
+#include "list.h"
+#include "sched.h"
+#include "interactive.h"
+#include "string.h"
+#include "error.h"
+
+struct i9e_private {
+       struct i9e_client_info *ici;
+       FILE *stderr_stream;
+       char empty_line[1000];
+       struct task task;
+       struct btr_node *stdout_btrn;
+       bool line_handler_running;
+       bool input_eof;
+       bool caught_sigint;
+};
+static struct i9e_private i9e_private, *i9ep = &i9e_private;
+
+static bool is_prefix(const char *partial, const char *full, size_t len)
+{
+       if (len == 0)
+               len = strlen(partial);
+       return !strncmp(partial, full, len);
+}
+
+/*
+ * Generator function for command completion. STATE lets us know whether
+ * to start from scratch; without any state (i.e. STATE == 0), then we
+ * start at the top of the list.
+ */
+static char *command_generator(const char *text, int state)
+{
+       static int list_index, len;
+       const char *name;
+       struct i9e_client_info *ici = i9ep->ici;
+
+       rl_attempted_completion_over = 1; /* disable filename completion */
+       /*
+        * If this is a new word to complete, initialize now. This includes
+        * saving the length of TEXT for efficiency, and initializing the index
+        * variable to 0.
+        */
+       if (state == 0) {
+               list_index = 0;
+               len = strlen(text);
+       }
+       /* Return the next name which partially matches from the command list. */
+       while ((name = ici->completers[list_index].name)) {
+               list_index++;
+               if (is_prefix(text, name, len))
+                       return para_strdup(name);
+       }
+       return NULL; /* no names matched */
+}
+
+static void reset_completion_result(struct i9e_completion_result *cr)
+{
+       cr->dont_append_space = false;
+       cr->filename_completion_desired = false;
+       cr->matches = NULL;
+}
+
+static void create_matches(struct i9e_completion_info *ci,
+               struct i9e_completer *completers,
+               struct i9e_completion_result *cr)
+{
+       int i, ret;
+
+       reset_completion_result(cr);
+
+       ret = create_argv(ci->buffer, " ", &ci->argv);
+       if (ret < 0 || !ci->argv[0])
+               return;
+
+       ci->argc = ret;
+       ci->word_num = compute_word_num(ci->buffer, " ", ci->point);
+       for (i = 0; completers[i].name; i++) {
+               if (strcmp(completers[i].name, ci->argv[0]) != 0)
+                       continue;
+               completers[i].completer(ci, cr);
+               break;
+       }
+       PARA_DEBUG_LOG("current word: %d (%s)\n", ci->word_num,
+               ci->argv[ci->word_num]);
+       if (cr->matches)
+               for (i = 0; cr->matches[i]; i++)
+                       PARA_DEBUG_LOG("match %d: %s\n", i, cr->matches[i]);
+}
+
+static char *completion_generator(const char *word, int state)
+{
+       static int list_index;
+       static char **argv, **matches;
+       struct i9e_completer *completers = i9ep->ici->completers;
+       struct i9e_completion_info ci = {
+               .word = (char *)word,
+               .point = rl_point,
+               .buffer = rl_line_buffer,
+       };
+       struct i9e_completion_result cr = {.matches = NULL};
+
+       if (state != 0)
+               goto out;
+       /* clean up previous matches and set defaults */
+       free(matches);
+       matches = NULL;
+       free_argv(argv);
+       argv = NULL;
+       list_index = 0;
+       rl_completion_append_character = ' ';
+       rl_completion_suppress_append = false;
+       rl_attempted_completion_over = true;
+
+       create_matches(&ci, completers, &cr);
+
+       matches = cr.matches;
+       argv = ci.argv;
+       rl_completion_suppress_append = cr.dont_append_space;
+       rl_attempted_completion_over = !cr.filename_completion_desired;
+out:
+       if (!matches)
+               return NULL;
+       return matches[list_index++];
+}
+
+/*
+ * Attempt to complete on the contents of TEXT. START and END bound the
+ * region of rl_line_buffer that contains the word to complete.  TEXT is
+ * the word to complete.  We can use the entire contents of rl_line_buffer
+ * in case we want to do some simple parsing. Return the array of matches,
+ * or NULL if there aren't any.
+ */
+static char **i9e_completer(const char *text, int start, __a_unused int end)
+{
+       struct i9e_client_info *ici = i9ep->ici;
+
+       if (!ici->completers)
+               return NULL;
+       /* Complete on command names if this is the first word in the line. */
+       if (start == 0)
+               return rl_completion_matches(text, command_generator);
+       return rl_completion_matches(text, completion_generator);
+}
+
+/**
+ * Prepare writing to stdout.
+ *
+ * \param producer The buffer tree node which produces output.
+ *
+ * The i9e subsystem maintains a buffer tree node which may be attached to
+ * another node which generates output (a "producer"). When attached, the i9e
+ * buffer tree node copies the buffers generated by the producer to stdout.
+ *
+ * This function attaches the i9e input queue to an output queue of \a
+ * producer.
+ *
+ * \return Standard.
+ */
+void i9e_attach_to_stdout(struct btr_node *producer)
+{
+       assert(!i9ep->stdout_btrn);
+       i9ep->stdout_btrn = btr_new_node(&(struct btr_node_description)
+               EMBRACE(.name = "interactive_stdout", .parent = producer));
+}
+
+/**
+ * Reset the terminal and save the in-memory command line history.
+ *
+ * This should be called before the caller exits.
+ */
+void i9e_close(void)
+{
+       char *hf = i9ep->ici->history_file;
+
+       rl_deprep_terminal();
+       fprintf(i9ep->stderr_stream, "\n");
+       if (hf)
+               write_history(hf);
+       fclose(i9ep->stderr_stream);
+}
+
+static void wipe_bottom_line(void)
+{
+       fprintf(i9ep->stderr_stream, "\r%s\r", i9ep->empty_line);
+}
+
+static void clear_bottom_line(void)
+{
+       int point;
+       char *text;
+
+       if (rl_point == 0 && rl_end == 0)
+               return wipe_bottom_line();
+       /*
+        * We might have a multi-line input that needs to be wiped here, so the
+        * simple printf("\r<space>\r") is insufficient. To workaround this, we
+        * remove the whole line, redisplay and restore the killed text.
+        */
+       point = rl_point;
+       text = rl_copy_text(0, rl_end);
+       rl_kill_full_line(0, 0);
+       rl_redisplay();
+       wipe_bottom_line(); /* wipe out the prompt */
+       rl_insert_text(text);
+       rl_point = point;
+}
+
+static bool input_available(void)
+{
+       fd_set rfds;
+       struct timeval tv = {0, 0};
+       int ret;
+
+       FD_ZERO(&rfds);
+       FD_SET(i9ep->ici->fds[0], &rfds);
+       ret = para_select(1, &rfds, NULL, &tv);
+       return ret > 0;
+}
+
+static void i9e_line_handler(char *line)
+{
+       int ret;
+
+       i9ep->line_handler_running = true;
+       ret = i9ep->ici->line_handler(line);
+       i9ep->line_handler_running = false;
+       if (ret < 0)
+               PARA_WARNING_LOG("%s\n", para_strerror(-ret));
+       rl_set_prompt("");
+       if (line) {
+               if (!*line)
+                       rl_set_prompt(i9ep->ici->prompt);
+               else
+                       add_history(line);
+               free(line);
+       } else {
+               rl_set_prompt("");
+               i9ep->input_eof = true;
+       }
+}
+
+static void i9e_input(void)
+{
+       do {
+               rl_callback_read_char();
+       } while (input_available());
+}
+
+static void i9e_post_select(struct sched *s, struct task *t)
+{
+       int ret;
+       struct btr_node *btrn = i9ep->stdout_btrn;
+       struct i9e_client_info *ici = i9ep->ici;
+       char *buf;
+       size_t sz;
+
+       if (i9ep->input_eof) {
+               t->error = -E_I9E_EOF;
+               return;
+       }
+       if (!btrn) {
+               i9ep->caught_sigint = false;
+               if (FD_ISSET(ici->fds[0], &s->rfds))
+                       i9e_input();
+               return;
+       }
+       if (i9ep->caught_sigint)
+               goto rm_btrn;
+       ret = btr_node_status(i9ep->stdout_btrn, 0, BTR_NT_LEAF);
+       if (ret < 0)
+               goto rm_btrn;
+       sz = btr_next_buffer(btrn, &buf);
+       if (sz == 0)
+               goto out;
+       ret = write_nonblock(ici->fds[1], buf, sz);
+       if (ret < 0)
+               goto rm_btrn;
+       btr_consume(btrn, ret);
+       goto out;
+rm_btrn:
+       btr_remove_node(btrn);
+       btr_free_node(btrn);
+       i9ep->stdout_btrn = NULL;
+       rl_set_prompt(i9ep->ici->prompt);
+       rl_forced_update_display();
+out:
+       t->error = 0;
+}
+
+static void i9e_pre_select(struct sched *s, __a_unused struct task *t)
+{
+       int ret;
+
+       if (i9ep->input_eof || i9ep->caught_sigint) {
+               sched_min_delay(s);
+               return;
+       }
+       if (i9ep->stdout_btrn) {
+               ret = btr_node_status(i9ep->stdout_btrn, 0, BTR_NT_LEAF);
+               if (ret < 0) {
+                       sched_min_delay(s);
+                       return;
+               }
+               if (ret > 0)
+                       para_fd_set(i9ep->ici->fds[1], &s->wfds, &s->max_fileno);
+       }
+       /*
+        * fd[0] might have been reset to blocking mode if our job was moved to
+        * the background due to CTRL-Z or SIGSTOP, so set the fd back to
+        * nonblocking mode.
+        */
+       ret = mark_fd_nonblocking(i9ep->ici->fds[0]);
+       if (ret < 0)
+               PARA_WARNING_LOG("set to nonblock failed: (fd0 %d, %s)\n",
+                       i9ep->ici->fds[0], para_strerror(-ret));
+       para_fd_set(i9ep->ici->fds[0], &s->rfds, &s->max_fileno);
+       return;
+}
+
+static void update_winsize(void)
+{
+       struct winsize w;
+       int ret = ioctl(i9ep->ici->fds[2], TIOCGWINSZ, (char *)&w);
+       int num_columns = 80;
+
+       if (ret >= 0) {
+               assert(w.ws_col < sizeof(i9ep->empty_line));
+               num_columns = w.ws_col;
+       }
+       memset(i9ep->empty_line, ' ', num_columns);
+       i9ep->empty_line[num_columns] = '\0';
+}
+
+/**
+ * Register the i9e task and initialize readline.
+ *
+ * \param ici The i9e configuration parameters set by the caller.
+ * \param s The scheduler instance to add the i9e task to.
+ *
+ * The caller must allocate and initialize the structure \a ici points to.
+ *
+ * \return Standard.
+ * \sa \ref register_task().
+ */
+int i9e_open(struct i9e_client_info *ici, struct sched *s)
+{
+       int ret;
+
+       if (!isatty(ici->fds[0]))
+               return -E_I9E_SETUPTERM;
+       ret = mark_fd_nonblocking(ici->fds[0]);
+       if (ret < 0)
+               return ret;
+       ret = mark_fd_nonblocking(ici->fds[1]);
+       if (ret < 0)
+               return ret;
+       i9ep->task.pre_select = i9e_pre_select;
+       i9ep->task.post_select = i9e_post_select;
+       register_task(s, &i9ep->task);
+       rl_readline_name = "para_i9e";
+       rl_basic_word_break_characters = " ";
+       rl_attempted_completion_function = i9e_completer;
+       i9ep->ici = ici;
+       i9ep->stderr_stream = fdopen(ici->fds[2], "w");
+
+       if (ici->history_file)
+               read_history(ici->history_file);
+       update_winsize();
+       rl_callback_handler_install(i9ep->ici->prompt, i9e_line_handler);
+       return 1;
+}
+
+static void reset_line_state(void)
+{
+       if (i9ep->line_handler_running)
+               return;
+       rl_on_new_line();
+       rl_reset_line_state();
+       rl_forced_update_display();
+}
+
+/**
+ * The log function of the i9e subsystem.
+ *
+ * \param ll Severity log level.
+ * \param fmt Printf-like format string.
+ *
+ * This clears the bottom line of the terminal if necessary and writes the
+ * string given by \a fmt to fd[2], where fd[] is the array provided earlier in
+ * \ref i9e_open().
+ */
+__printf_2_3 void i9e_log(int ll, const char* fmt,...)
+{
+       va_list argp;
+
+       if (ll < i9ep->ici->loglevel)
+               return;
+       if (i9ep->line_handler_running == false)
+               clear_bottom_line();
+       va_start(argp, fmt);
+       vfprintf(i9ep->stderr_stream, fmt, argp);
+       va_end(argp);
+       reset_line_state();
+}
+
+/**
+ * Tell i9e that the caller received a signal.
+ *
+ * \param sig_num The number of the signal received.
+ *
+ * Currently the function only cares about \p SIGINT, but this may change.
+ */
+void i9e_signal_dispatch(int sig_num)
+{
+       if (sig_num == SIGINT) {
+               fprintf(i9ep->stderr_stream, "\n");
+               rl_replace_line ("", false /* clear_undo */);
+               reset_line_state();
+               i9ep->caught_sigint = true;
+       }
+}
+
+/**
+ * Wrapper for select(2) which does not restart on interrupts.
+ *
+ * \param n \sa \ref para_select().
+ * \param readfds \sa \ref para_select().
+ * \param writefds \sa \ref para_select().
+ * \param timeout_tv \sa \ref para_select().
+ *
+ * \return \sa \ref para_select().
+ *
+ * The only difference between this function and \ref para_select() is that
+ * \ref i9e_select() returns zero if the select call returned \p EINTR.
+ */
+int i9e_select(int n, fd_set *readfds, fd_set *writefds,
+               struct timeval *timeout_tv)
+{
+       int ret = select(n, readfds, writefds, NULL, timeout_tv);
+
+       if (ret < 0) {
+               if (errno == EINTR)
+                       ret = 0;
+               else
+                       ret = -ERRNO_TO_PARA_ERROR(errno);
+       }
+       return ret;
+}
+
+/**
+ * Return the possible completions for a given word.
+ *
+ * \param word The word to complete.
+ * \param string_list All possible words in this context.
+ * \param result String list is returned here.
+ *
+ * This function never fails. If no completion was found, a string list of
+ * length zero is returned. In any case, the result must be freed by the caller
+ * using \ref free_argv().
+ *
+ * This function is independent of readline and may be called before
+ * i9e_open().
+ *
+ * return The number of possible completions.
+ */
+int i9e_extract_completions(const char *word, char **string_list,
+               char ***result)
+{
+       char **matches = para_malloc(sizeof(char *));
+       int match_count = 0, matches_len = 1;
+       char **p;
+       int len = strlen(word);
+
+       for (p = string_list; *p; p++) {
+               if (!is_prefix(word, *p, len))
+                       continue;
+               match_count++;
+               if (match_count >= matches_len) {
+                       matches_len *= 2;
+                       matches = para_realloc(matches,
+                               matches_len * sizeof(char *));
+               }
+               matches[match_count - 1] = para_strdup(*p);
+       }
+       matches[match_count] = NULL;
+       *result = matches;
+       return match_count;
+}
+
+/**
+ * Return the list of partially matching words.
+ *
+ * \param word The command to complete.
+ * \param completers The array containing all command names.
+ *
+ * This is similar to \ref i9e_extract_completions(), but completes on the
+ * command names in \a completers.
+ *
+ * \return See \ref i9e_extract_completions().
+ */
+char **i9e_complete_commands(const char *word, struct i9e_completer *completers)
+{
+       char **matches;
+       const char *cmd;
+       int i, match_count, len = strlen(word);
+
+       /*
+        * In contrast to completing against an arbitrary string list, here we
+        * know all possible completions and expect that there will not be many
+        * of them. So it should be OK to iterate twice over all commands which
+        * simplifies the code a bit.
+        */
+       for (i = 0, match_count = 0; (cmd = completers[i].name); i++) {
+               if (is_prefix(word, cmd, len))
+                       match_count++;
+       }
+       matches = para_malloc((match_count + 1) * sizeof(*matches));
+       for (i = 0, match_count = 0; (cmd = completers[i].name); i++)
+               if (is_prefix(word, cmd, len))
+                       matches[match_count++] = para_strdup(cmd);
+       matches[match_count] = NULL;
+       return matches;
+}
+
+/**
+ * Complete according to the given options.
+ *
+ * \param opts All available options.
+ * \param ci Information which was passed to the completer.
+ * \param cr Result pointer.
+ *
+ * This convenience helper can be used to complete an option. The array of all
+ * possible options is passed as the first argument. Flags, i.e. options
+ * without an argument, are expected to be listed as strings of type "-X" in \a
+ * opts while options which require an argument should be passed with a
+ * trailing "=" character like "-X=".
+ *
+ * If the word can be uniquely completed to a flag option, an additional space
+ * character is appended to the output. For non-flag options no space character
+ * is appended.
+ */
+void i9e_complete_option(char **opts, struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr)
+{
+       int num_matches;
+
+       num_matches = i9e_extract_completions(ci->word, opts, &cr->matches);
+       if (num_matches == 1) {
+               char *opt = cr->matches[0];
+               char c = opt[strlen(opt) - 1];
+               if (c == '=')
+                       cr->dont_append_space = true;
+       }
+}
+
+/**
+ * Print possible completions to stdout.
+ *
+ * \param completers The array of completion functions.
+ *
+ * At the end of the output a line starting with "-o=", followed by the
+ * (possibly empty) list of completion options is printed. Currently, the only
+ * two completion options are "nospace" and "filenames". The former indicates
+ * that no space should be appended even for a unique match while the latter
+ * indicates that usual filename completion should be performed in addition to
+ * the previously printed options.
+ *
+ * \return Standard.
+ */
+int i9e_print_completions(struct i9e_completer *completers)
+{
+       struct i9e_completion_result cr;
+       struct i9e_completion_info ci;
+       char *buf;
+       const char *end, *p;
+       int i, n, ret;
+
+       reset_completion_result(&cr);
+       buf = getenv("COMP_POINT");
+       ci.point = buf? atoi(buf) : 0;
+       ci.buffer = para_strdup(getenv("COMP_LINE"));
+
+       ci.argc = create_argv(ci.buffer, " ", &ci.argv);
+       ci.word_num = compute_word_num(ci.buffer, " ", ci.point);
+
+       end = ci.buffer + ci.point;
+       for (p = end; p > ci.buffer && *p != ' '; p--)
+               ; /* nothing */
+       if (*p == ' ')
+               p++;
+
+       n = end - p + 1;
+       ci.word = para_malloc(n + 1);
+       strncpy(ci.word, p, n);
+       ci.word[n] = '\0';
+
+       PARA_DEBUG_LOG("line: %s, point: %d (%c), wordnum: %d, word: %s\n",
+               ci.buffer, ci.point, ci.buffer[ci.point], ci.word_num, ci.word);
+       if (ci.word_num == 0)
+               cr.matches = i9e_complete_commands(ci.word, completers);
+       else
+               create_matches(&ci, completers, &cr);
+       ret = 0;
+       if (cr.matches && cr.matches[0]) {
+               for (i = 0; cr.matches[i]; i++)
+                       printf("%s\n", cr.matches[i]);
+               ret = 1;
+       }
+       printf("-o=");
+       if (cr.dont_append_space)
+               printf("nospace");
+       if (cr.filename_completion_desired)
+               printf(",filenames");
+       printf("\n");
+       free_argv(cr.matches);
+       free_argv(ci.argv);
+       free(ci.buffer);
+       free(ci.word);
+       return ret;
+}
diff --git a/interactive.h b/interactive.h
new file mode 100644 (file)
index 0000000..be42c91
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2011 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file interactive.h Public API for interactive sessions. */
+
+/* Interactive is hard to spell, lets' write i9e. */
+
+/** Structure passed as input to the completers. */
+struct i9e_completion_info {
+       char *buffer; /**< The full line. */
+       char *word; /**< The word the cursor is in. */
+       int point; /**< Cursor position. */
+       char **argv; /**< Vector of words in \a buffer. */
+       int argc; /**< Number of elements(words) in argv. */
+       int word_num; /**< The cursor is on this word. */
+};
+
+/** Completion information returned by the completers. */
+struct i9e_completion_result {
+       /** NULL-terminated array of possible completions. */
+       char **matches;
+       /** Whether standard filename completion should be performed. */
+       bool filename_completion_desired;
+       /** Suppress adding a space character after the completed word. */
+       bool dont_append_space;
+};
+
+/**
+ * Define a completer which does nothing.
+ *
+ * \param name Determines the name of the function to be defined.
+ */
+#define I9E_DUMMY_COMPLETER(name) void name ## _completer( \
+               __a_unused struct i9e_completion_info *ciname, \
+               struct i9e_completion_result *result) {result->matches = NULL;}
+
+/**
+ * A completer is simply a function pointer and name of the command for which
+ * it performs completion.
+ */
+struct i9e_completer {
+       /** The command for which this completer provides completion. */
+       const char *name;
+       /** The completer returns all possible completions via the second parameter. */
+       void (*completer)(struct i9e_completion_info *, struct i9e_completion_result *);
+};
+
+/**
+ * The i9e configuration settings of the client.
+ *
+ * A structure of this type must be allocated and filled in by the client
+ * before it is passed to the i9e subsystem via \ref i9e_open().
+ */
+struct i9e_client_info {
+       /** Threshold for i9e_log(). */
+       int loglevel;
+       /** Complete input lines are passed to this callback function. */
+       int (*line_handler)(char *line);
+       /** File descriptors to use for input/output/log. */
+       int fds[3];
+       /** Text of the current prompt. */
+       char *prompt;
+       /** Where to store the readline history. */
+       char *history_file;
+       /**
+        * The array of completers, one per command. This is used for
+        * completing the first word (the command) and for calling the right
+        * completer if the cursor is not on the first word.
+        */
+       struct i9e_completer *completers;
+};
+
+int i9e_open(struct i9e_client_info *ici, struct sched *s);
+void i9e_attach_to_stdout(struct btr_node *producer);
+void i9e_close(void);
+void i9e_signal_dispatch(int sig_num);
+__printf_2_3 void i9e_log(int ll, const char* fmt,...);
+int i9e_select(int n, fd_set *readfds, fd_set *writefds,
+               struct timeval *timeout_tv);
+int i9e_extract_completions(const char *word, char **string_list,
+               char ***result);
+char **i9e_complete_commands(const char *word, struct i9e_completer *completers);
+void i9e_complete_option(char **opts, struct i9e_completion_info *ci,
+               struct i9e_completion_result *cr);
+int i9e_print_completions(struct i9e_completer *completers);
diff --git a/para.h b/para.h
index bc6aa929539ee4a9c1abb8e0d2b14695d88870cc..96492baaf35ce666c37e31c6a60e8af5a6573333 100644 (file)
--- a/para.h
+++ b/para.h
        typeof(x) _x = (x); \
        _x > 0? _x : -_x; })
 
+
+extern __printf_2_3 void (*para_log)(int, const char*, ...);
 /**
  * Define a standard log function that always writes to stderr.
  *
+ * \param funcname The name of the function to be defined.
+ *
  * \param loglevel_barrier If the loglevel of the current message
  * is less than that, the message is going to be ignored.
  *
  */
-#define INIT_STDERR_LOGGING(loglevel_barrier) \
-       __printf_2_3 void para_log(int ll, const char* fmt,...) \
+#define DEFINE_STDERR_LOGGER(funcname, loglevel_barrier) \
+       __printf_2_3 void funcname(int ll, const char* fmt,...) \
        { \
                va_list argp; \
                if (ll < loglevel_barrier) \
@@ -66,6 +70,9 @@
                vfprintf(stderr, fmt, argp); \
                va_end(argp); \
        }
+#define INIT_STDERR_LOGGING(loglevel_barrier) \
+       DEFINE_STDERR_LOGGER(stderr_log, loglevel_barrier); \
+       __printf_2_3 void (*para_log)(int, const char*, ...) = stderr_log;
 
 /** Sent by para_client to initiate the authentication procedure. */
 #define AUTH_REQUEST_MSG "auth rsa "
@@ -105,8 +112,6 @@ extern const char *status_item_list[];
 int for_each_stat_item(char *item_buf, size_t num_bytes,
        int (*item_handler)(int, char *));
 
-__printf_2_3 void para_log(int, const char*, ...);
-
 /**
  * Write a log message to a dynamically allocated string.
  *
diff --git a/recv.c b/recv.c
index 52f457fc29b68e61ba017079511145f4e3120885..9b63f517cd77cccd27db0cd4ddcc1dae99c8e082 100644 (file)
--- a/recv.c
+++ b/recv.c
@@ -102,12 +102,12 @@ int main(int argc, char *argv[])
        sot.btrn = btr_new_node(&(struct btr_node_description)
                EMBRACE(.parent = rn.btrn, .name = "stdout"));
        stdout_set_defaults(&sot);
-       register_task(&sot.task);
+       register_task(&s, &sot.task);
 
        rn.task.pre_select = r->pre_select;
        rn.task.post_select = r->post_select;
        sprintf(rn.task.status, "%s", r->name);
-       register_task(&rn.task);
+       register_task(&s, &rn.task);
 
        ret = schedule(&s);
 out:
diff --git a/sched.c b/sched.c
index 66a17418027a5e9fa2d92bee412cf682561115b9..041cf417723bd848ea56d2c7eb4935943abeeaef 100644 (file)
--- a/sched.c
+++ b/sched.c
@@ -20,9 +20,6 @@
 #include "time.h"
 #include "error.h"
 
-static struct list_head pre_select_list, post_select_list;
-static int initialized;
-
 static struct timeval now_struct;
 struct timeval *now = &now_struct;
 
@@ -36,8 +33,6 @@ struct timeval *now = &now_struct;
  */
 static void unregister_task(struct task *t)
 {
-       if (!initialized)
-               return;
        assert(t->error < 0);
        PARA_INFO_LOG("unregistering %s (%s)\n", t->status,
                para_strerror(-t->error));
@@ -56,7 +51,7 @@ static inline bool timeout_is_zero(struct sched *s)
 static void sched_preselect(struct sched *s)
 {
        struct task *t, *tmp;
-       list_for_each_entry_safe(t, tmp, &pre_select_list, pre_select_node) {
+       list_for_each_entry_safe(t, tmp, &s->pre_select_list, pre_select_node) {
                if (t->error < 0) {
                        unregister_task(t);
                        continue;
@@ -93,7 +88,7 @@ static void sched_post_select(struct sched *s)
 {
        struct task *t, *tmp;
 
-       list_for_each_entry_safe(t, tmp, &post_select_list, post_select_node) {
+       list_for_each_entry_safe(t, tmp, &s->post_select_list, post_select_node) {
                if (t->error >= 0)
                        call_post_select(s, t);
 //             PARA_INFO_LOG("%s: %d\n", t->status, t->ret);
@@ -105,7 +100,7 @@ static void sched_post_select(struct sched *s)
                 * this case t has been unregistered already, so we must not
                 * unregister it again.
                 */
-               if (list_empty(&post_select_list))
+               if (list_empty(&s->post_select_list))
                        return;
                unregister_task(t);
        }
@@ -130,8 +125,6 @@ int schedule(struct sched *s)
 {
        int ret;
 
-       if (!initialized)
-               return -E_NOT_INITIALIZED;
        if (!s->select_function)
                s->select_function = para_select;
 again:
@@ -141,7 +134,7 @@ again:
        s->max_fileno = -1;
        gettimeofday(now, NULL);
        sched_preselect(s);
-       if (list_empty(&pre_select_list) && list_empty(&post_select_list))
+       if (list_empty(&s->pre_select_list) && list_empty(&s->post_select_list))
                return 0;
        if (!timeout_is_zero(s)) {
                ret = s->select_function(s->max_fileno + 1, &s->rfds, &s->wfds,
@@ -164,44 +157,36 @@ again:
                FD_ZERO(&s->wfds);
        }
        sched_post_select(s);
-       if (list_empty(&pre_select_list) && list_empty(&post_select_list))
+       if (list_empty(&s->pre_select_list) && list_empty(&s->post_select_list))
                return 0;
        goto again;
 }
 
-/*
- * Initialize the paraslash scheduler.
- */
-static void init_sched(void)
-{
-       PARA_INFO_LOG("initializing scheduler\n");
-       INIT_LIST_HEAD(&pre_select_list);
-       INIT_LIST_HEAD(&post_select_list);
-       initialized = 1;
-}
-
 /**
  * Add a task to the scheduler.
  *
- * \param t the task to add
+ * \param t The task to add.
+ * \param s The scheduler instance to add the task to.
  *
  * If the pre_select pointer of \a t is not \p NULL, it is added to
  * the pre_select list of the scheduler. Same goes for post_select.
  *
  * \sa task::pre_select, task::post_select
  */
-void register_task(struct task *t)
+void register_task(struct sched *s, struct task *t)
 {
-       if (!initialized)
-               init_sched();
        PARA_INFO_LOG("registering %s (%p)\n", t->status, t);
+       if (!s->pre_select_list.next)
+               INIT_LIST_HEAD(&s->pre_select_list);
+       if (!s->post_select_list.next)
+               INIT_LIST_HEAD(&s->post_select_list);
        if (t->pre_select) {
                PARA_DEBUG_LOG("pre_select: %p\n", &t->pre_select);
-               list_add_tail(&t->pre_select_node, &pre_select_list);
+               list_add_tail(&t->pre_select_node, &s->pre_select_list);
        }
        if (t->post_select) {
                PARA_DEBUG_LOG("post_select: %p\n", &t->post_select);
-               list_add_tail(&t->post_select_node, &post_select_list);
+               list_add_tail(&t->post_select_node, &s->post_select_list);
        }
 }
 
@@ -212,21 +197,18 @@ void register_task(struct task *t)
  * \a pre_select_list and the \a post_select_list are empty. This function
  * must be called from the post_select (rather than the pre_select) method.
  */
-void sched_shutdown(void)
+void sched_shutdown(struct sched *s)
 {
        struct task *t, *tmp;
 
-       if (!initialized)
-               return;
-       list_for_each_entry_safe(t, tmp, &pre_select_list, pre_select_node) {
+       list_for_each_entry_safe(t, tmp, &s->pre_select_list, pre_select_node) {
                t->error = -E_SCHED_SHUTDOWN;
                unregister_task(t);
        }
-       list_for_each_entry_safe(t, tmp, &post_select_list, post_select_node) {
+       list_for_each_entry_safe(t, tmp, &s->post_select_list, post_select_node) {
                t->error = -E_SCHED_SHUTDOWN;
                unregister_task(t);
        }
-       initialized = 0;
 }
 
 /**
@@ -237,20 +219,18 @@ void sched_shutdown(void)
  * Each entry of the list contains an identifier which is simply a hex number.
  * The result is dynamically allocated and must be freed by the caller.
  */
-char *get_task_list(void)
+char *get_task_list(struct sched *s)
 {
        struct task *t, *tmp;
        char *msg = NULL;
 
-       if (!initialized)
-               return NULL;
-       list_for_each_entry_safe(t, tmp, &pre_select_list, pre_select_node) {
+       list_for_each_entry_safe(t, tmp, &s->pre_select_list, pre_select_node) {
                char *tmp_msg;
                tmp_msg = make_message("%s%p\tpre\t%s\n", msg? msg : "", t, t->status);
                free(msg);
                msg = tmp_msg;
        }
-       list_for_each_entry_safe(t, tmp, &post_select_list, post_select_node) {
+       list_for_each_entry_safe(t, tmp, &s->post_select_list, post_select_node) {
                char *tmp_msg;
 //             if (t->pre_select)
 //                     continue;
diff --git a/sched.h b/sched.h
index ea9578f7b05b3bae17e69e87543675e902d0c590..b3df0b66b180bd1f3f2f48f784c07a187fbac712 100644 (file)
--- a/sched.h
+++ b/sched.h
@@ -29,6 +29,10 @@ struct sched {
        int max_fileno;
        /** If non-NULL, use this function instead of para_select. */
        int (*select_function)(int, fd_set *, fd_set *, struct timeval *);
+       /** Currently active pre_select functions. */
+       struct list_head pre_select_list;
+       /** Currently active post_select functions. */
+       struct list_head post_select_list;
 };
 
 /**
@@ -74,10 +78,10 @@ struct task {
  */
 extern struct timeval *now;
 
-void register_task(struct task *t);
+void register_task(struct sched *s, struct task *t);
 int schedule(struct sched *s);
-char *get_task_list(void);
-void sched_shutdown(void);
+char *get_task_list(struct sched *s);
+void sched_shutdown(struct sched *s);
 void sched_min_delay(struct sched *s);
 void sched_request_timeout(struct timeval *to, struct sched *s);
 void sched_request_timeout_ms(long unsigned ms, struct sched *s);
index e786f996ad3b85218d9953a01fee6a86f34b6de4..5bd7d5708ac835553125d1591407d63e79d0add4 100644 (file)
@@ -22,6 +22,7 @@
 #include "send.h"
 #include "close_on_fork.h"
 #include "chunk_queue.h"
+#include "sched.h"
 #include "vss.h"
 
 /** Clients will be kicked if there are more than that many bytes pending. */
index 7f020c8f73f34674a0ff644aa84e73a9096962b1..ef79a2e88d7577dc882fba229d9141a0c55a1549 100644 (file)
--- a/server.c
+++ b/server.c
@@ -79,6 +79,7 @@
 #include "server.h"
 #include "list.h"
 #include "send.h"
+#include "sched.h"
 #include "vss.h"
 #include "config.h"
 #include "close_on_fork.h"
 #include "daemon.h"
 #include "ipc.h"
 #include "fd.h"
-#include "sched.h"
 #include "signal.h"
 #include "user_list.h"
 #include "color.h"
 #include "version.h"
 
+__printf_2_3 void (*para_log)(int, const char*, ...) = daemon_log;
+
 /** Define the array of error lists needed by para_server. */
 INIT_SERVER_ERRLISTS;
 
@@ -121,6 +123,7 @@ int mmd_mutex;
 /** The file containing user information (public key, permissions). */
 static char *user_list_file = NULL;
 
+static struct sched sched;
 
 /** The task responsible for server command handling. */
 struct server_command_task {
@@ -344,7 +347,7 @@ static void init_signal_task(void)
        para_install_sighandler(SIGCHLD);
        para_sigaction(SIGPIPE, SIG_IGN);
        add_close_on_fork_list(st->fd);
-       register_task(&st->task);
+       register_task(&sched, &st->task);
 }
 
 static void command_pre_select(struct sched *s, struct task *t)
@@ -427,7 +430,7 @@ static void init_server_command_task(int argc, char **argv)
                goto err;
        add_close_on_fork_list(sct->listen_fd); /* child doesn't need the listener */
        sprintf(sct->task.status, "server command task");
-       register_task(&sct->task);
+       register_task(&sched, &sct->task);
        return;
 err:
        PARA_EMERG_LOG("%s\n", para_strerror(-ret));
@@ -517,7 +520,7 @@ static void server_init(int argc, char **argv)
        init_signal_task();
        para_unblock_signal(SIGCHLD);
        PARA_NOTICE_LOG("initializing virtual streaming system\n");
-       init_vss_task(afs_socket);
+       init_vss_task(afs_socket, &sched);
        init_server_command_task(argc, argv);
        if (conf.daemon_given)
                kill(getppid(), SIGTERM);
@@ -569,16 +572,13 @@ static int server_select(int max_fileno, fd_set *readfds, fd_set *writefds,
 int main(int argc, char *argv[])
 {
        int ret;
-       static struct sched s = {
-               .default_timeout = {
-                       .tv_sec = 1,
-                       .tv_usec = 0
-               },
-               .select_function = server_select
-       };
+
+       sched.default_timeout.tv_sec = 1;
+       sched.select_function = server_select;
+
        server_init(argc, argv);
        mutex_lock(mmd_mutex);
-       ret = schedule(&s);
+       ret = schedule(&sched);
        if (ret < 0) {
                PARA_EMERG_LOG("%s\n", para_strerror(-ret));
                exit(EXIT_FAILURE);
index f7d70353db977d6543055c19c7bd68a17b83a3ce..3bddd5e80896e843470687671a05cb0ef09051e2 100644 (file)
--- a/string.c
+++ b/string.c
@@ -711,6 +711,32 @@ out:
        return ret;
 }
 
+/**
+ * Get the number of the word the cursor is on.
+ *
+ * \param buf The zero-terminated line buffer.
+ * \param delim Characters that separate words.
+ * \param point The cursor position.
+ *
+ * \return Zero-based word number.
+ */
+int compute_word_num(const char *buf, const char *delim, int point)
+{
+       int ret, num_words;
+       const char *p;
+       char *word;
+
+       for (p = buf, num_words = 0; ; p += ret, num_words++) {
+               ret = get_next_word(p, delim, &word);
+               if (ret <= 0)
+                       break;
+               free(word);
+               if (p + ret >= buf + point)
+                       break;
+       }
+       return num_words;
+}
+
 /**
  * Free an array of words created by create_argv().
  *
index 403b9c6f15d15404076eeed7ea641b8b22ed600f..30af1fdb647f6701086b235324a6ff2867210822 100644 (file)
--- a/string.h
+++ b/string.h
@@ -83,5 +83,6 @@ int create_argv(const char *buf, const char *delim, char ***result);
 void free_argv(char **argv);
 int para_regcomp(regex_t *preg, const char *regex, int cflags);
 void freep(void *arg);
+int compute_word_num(const char *buf, const char *delim, int offset);
 char *safe_strdup(const char *src, size_t len);
 char *key_value_copy(const char *src, size_t len, const char *key);
index 7a1a89f73e1ab79d91fa2e0978072eaf9e10fc46..880d7957dbd4430819c917cb4b6a9533c96d321c 100644 (file)
 #include "server.h"
 #include "list.h"
 #include "send.h"
+#include "sched.h"
 #include "vss.h"
 #include "portable_io.h"
 #include "net.h"
 #include "fd.h"
-#include "sched.h"
 #include "close_on_fork.h"
 
 /**
diff --git a/vss.c b/vss.c
index 4a8aafa8d6e0c2ae239d09342ac10f3b08263828..17d46919b53ea962aa7c52a6c2ed88d251f1dbf7 100644 (file)
--- a/vss.c
+++ b/vss.c
 #include "server.cmdline.h"
 #include "list.h"
 #include "send.h"
+#include "sched.h"
 #include "vss.h"
 #include "ipc.h"
 #include "fd.h"
-#include "sched.h"
 
 extern struct misc_meta_data *mmd;
 
@@ -1157,11 +1157,12 @@ static void vss_post_select(struct sched *s, struct task *t)
  * Initialize the virtual streaming system task.
  *
  * \param afs_socket The fd for communication with afs.
+ * \param s The scheduler to register the vss task to.
  *
  * This also initializes all supported senders and starts streaming
  * if the --autoplay command line flag was given.
  */
-void init_vss_task(int afs_socket)
+void init_vss_task(int afs_socket, struct sched *s)
 {
        static struct vss_task vss_task_struct, *vsst = &vss_task_struct;
        int i;
@@ -1194,5 +1195,5 @@ void init_vss_task(int afs_socket)
                        &vsst->data_send_barrier);
        }
        sprintf(vsst->task.status, "vss task");
-       register_task(&vsst->task);
+       register_task(s, &vsst->task);
 }
diff --git a/vss.h b/vss.h
index bd798b0cfdaf454faee5816c3b16ffbed99e112b..ae53f6eccf8327c6035c6f33dceb01b0268e3616 100644 (file)
--- a/vss.h
+++ b/vss.h
@@ -6,7 +6,7 @@
 
 /** \file vss.h Exported functions from vss.c (para_server). */
 
-void init_vss_task(int afs_socket);
+void init_vss_task(int afs_socket, struct sched *s);
 unsigned int vss_playing(void);
 unsigned int vss_next(void);
 unsigned int vss_repos(void);
index 7eecdbb31ad09169666f4d2e353f215432e12961..041457e6157a585cd479edba8e65ab45fcd5e207 100644 (file)
@@ -115,6 +115,9 @@ All connections between para_server and para_client are encrypted
 with a symmetric RC4 session key. For each user of paraslash you must
 create a public/secret RSA key pair for authentication.
 
+If para_client is started without non-option arguments, an interactive
+session (shell) is started. Command history and command completion are
+supported through libreadline.
 
 *para_audiod*
 
@@ -135,7 +138,8 @@ socket credentials, if available.
 
 The client program which talks to para_audiod. Used to control
 para_audiod, to receive status info, or to grab the stream at any
-point of the decoding process.
+point of the decoding process. Like para_client, para_audioc supports
+interactive sessions on systems with libreadline.
 
 *para_recv*
 
@@ -266,6 +270,10 @@ Optional:
        libao). Needed to build the ao writer (ESD, PulseAudio,...).
        Debian package: libao-dev.
 
+       - XREFERENCE(http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html,
+       GNU Readline). If this library (libreadline-dev) is installed,
+       para_client and para_audioc support interactive sessions.
+
 Installation
 ~~~~~~~~~~~~
 
diff --git a/write.c b/write.c
index 3b5f2d193f31ddc1d8c130703637810f807f255f..afaad951922b0e02a973913327e2d92ee39d2631 100644 (file)
--- a/write.c
+++ b/write.c
@@ -186,7 +186,7 @@ __noreturn static void print_help_and_die(void)
  * \return Standard.
  */
 static void setup_writer_node(const char *arg, struct btr_node *parent,
-               struct writer_node *wn)
+               struct writer_node *wn, struct sched *s)
 {
        if (arg)
                wn->conf = check_writer_arg_or_die(arg, &wn->writer_num);
@@ -194,7 +194,7 @@ static void setup_writer_node(const char *arg, struct btr_node *parent,
                wn->writer_num = DEFAULT_WRITER;
                wn->conf = writers[DEFAULT_WRITER].parse_config_or_die("");
        }
-       register_writer_node(wn, parent);
+       register_writer_node(wn, parent, s);
 }
 
 static int setup_and_schedule(void)
@@ -208,7 +208,7 @@ static int setup_and_schedule(void)
        sit.btrn = btr_new_node(&(struct btr_node_description)
                EMBRACE(.name = "stdin"));
        stdin_set_defaults(&sit);
-       register_task(&sit.task);
+       register_task(&s, &sit.task);
 
        cwt->state = CWS_NEED_HEADER;
        cwt->min_iqs = WAV_HEADER_LEN;
@@ -219,17 +219,17 @@ static int setup_and_schedule(void)
        cwt->task.pre_select = check_wav_pre_select;
        cwt->task.post_select = check_wav_post_select;
        cwt->task.error = 0;
-       register_task(&cwt->task);
+       register_task(&s, &cwt->task);
 
        if (!conf.writer_given) {
                wns = para_calloc(sizeof(*wns));
-               setup_writer_node(NULL, cwt->btrn, wns);
+               setup_writer_node(NULL, cwt->btrn, wns, &s);
                i = 1;
        } else {
                wns = para_calloc(conf.writer_given * sizeof(*wns));
                for (i = 0; i < conf.writer_given; i++)
                        setup_writer_node(conf.writer_arg[i], cwt->btrn,
-                               wns + i);
+                               wns + i, &s);
        }
 
        s.default_timeout.tv_sec = 10;
index b85699367acd45d88eb4044da6450d2717f48437..71b6664d012d80cb397e524c3370347bb87a3956 100644 (file)
@@ -77,11 +77,13 @@ void *check_writer_arg_or_die(const char *wa, int *writer_num)
  *
  * \param wn The writer node to open.
  * \param parent The parent btr node (the source for the writer node).
+ * \param s The scheduler instance to register the task to.
  *
  * The configuration of the writer node stored in \p wn->conf must be
  * initialized before this function may be called.
  */
-void register_writer_node(struct writer_node *wn, struct btr_node *parent)
+void register_writer_node(struct writer_node *wn, struct btr_node *parent,
+               struct sched *s)
 {
        struct writer *w = writers + wn->writer_num;
        char *name = make_message("%s writer", writer_names[wn->writer_num]);
@@ -93,7 +95,7 @@ void register_writer_node(struct writer_node *wn, struct btr_node *parent)
        free(name);
        wn->task.post_select = w->post_select;
        wn->task.pre_select = w->pre_select;
-       register_task(&wn->task);
+       register_task(s, &wn->task);
 }
 
 /**
index 00ded8f37bb29f4219340286edee7f66a82fa0bf..68eb4063147fc4f41e4d66334aca10bab6d7d668 100644 (file)
@@ -9,7 +9,8 @@
 void writer_init(void);
 void *check_writer_arg_or_die(const char *wa, int *writer_num);
 void print_writer_helps(int detailed);
-void register_writer_node(struct writer_node *wn, struct btr_node *parent);
+void register_writer_node(struct writer_node *wn, struct btr_node *parent,
+               struct sched *s);
 void get_btr_sample_rate(struct btr_node *btrn, int32_t *result);
 void get_btr_channels(struct btr_node *btrn, int32_t *result);
 void get_btr_sample_format(struct btr_node *btrn, int32_t *result);