+ 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;
+
+ PARA_DEBUG_LOG("line: %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[0]);
+ return 1;
+}
+
+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 = i9e_signal_dispatch;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ sigaction(SIGINT, &act, NULL);
+ sched.select_function = i9e_select;
+
+ ret = i9e_open(&ici, &sched);