lightning-cli: print notifications (with '# ' prefix) if received.

This can be suppressed with -N.

Note that we wull get an error with older lightningd, but we ignore it
anyway.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Changelog-Added: cli: print notifications and progress bars if commands provide them.
This commit is contained in:
Rusty Russell
2020-10-12 16:03:50 +10:30
parent 806f208295
commit 41290a436f
7 changed files with 207 additions and 15 deletions

View File

@@ -15,6 +15,7 @@
#include <common/json.h>
#include <common/json_command.h>
#include <common/memleak.h>
#include <common/status_levels.h>
#include <common/utils.h>
#include <common/version.h>
#include <libgen.h>
@@ -470,6 +471,135 @@ static enum format choose_format(const char *resp,
return format;
}
static bool handle_notify(const char *buf, jsmntok_t *toks,
enum log_level notification_level,
bool *last_was_progress)
{
const jsmntok_t *id, *method, *params;
if (toks->type != JSMN_OBJECT)
return false;
id = json_get_member(buf, toks, "id");
if (id)
return false;
method = json_get_member(buf, toks, "method");
if (!method)
return false;
params = json_get_member(buf, toks, "params");
if (!params)
return false;
/* Print nothing if --notifications=none */
if (notification_level == LOG_LEVEL_MAX + 1)
return true;
/* We try to be robust if malformed */
if (json_tok_streq(buf, method, "message")) {
const jsmntok_t *message, *leveltok;
enum log_level level;
leveltok = json_get_member(buf, params, "level");
if (!leveltok
|| !log_level_parse(buf + leveltok->start,
leveltok->end - leveltok->start,
&level)
|| level < notification_level)
return true;
if (*last_was_progress)
printf("\n");
*last_was_progress = false;
message = json_get_member(buf, params, "message");
if (!message)
return true;
printf("# %.*s\n",
message->end - message->start,
buf + message->start);
} else if (json_tok_streq(buf, method, "progress")) {
const jsmntok_t *num, *total, *stage;
u32 n, tot;
char bar[60 + 1];
char totstr[STR_MAX_CHARS(u32)];
num = json_get_member(buf, params, "num");
total = json_get_member(buf, params, "total");
if (!num || !total)
return true;
if (!json_to_u32(buf, num, &n)
|| !json_to_u32(buf, total, &tot))
return true;
/* Ph3ar my gui skillz! */
printf("\r# ");
stage = json_get_member(buf, params, "stage");
if (stage) {
u32 stage_num, stage_total;
json_to_u32(buf, json_get_member(buf, stage, "num"),
&stage_num);
json_to_u32(buf, json_get_member(buf, stage, "total"),
&stage_total);
snprintf(totstr, sizeof(totstr), "%u", stage_total);
printf("Stage %*u/%s ",
(int)strlen(totstr), stage_num+1, totstr);
}
snprintf(totstr, sizeof(totstr), "%u", tot);
printf("%*u/%s ", (int)strlen(totstr), n+1, totstr);
memset(bar, ' ', sizeof(bar)-1);
memset(bar, '=', (double)strlen(bar) / (tot-1) * n);
bar[sizeof(bar)-1] = '\0';
printf("|%s|", bar);
/* Leave bar there if it's finished. */
if (n+1 == tot) {
printf("\n");
*last_was_progress = false;
} else {
fflush(stdout);
*last_was_progress = true;
}
}
return true;
}
static void enable_notifications(int fd)
{
const char *enable = "{ \"jsonrpc\": \"2.0\", \"method\": \"notifications\", \"id\": 0, \"params\": { \"enable\": true } }";
char rbuf[100];
if (!write_all(fd, enable, strlen(enable)))
err(ERROR_TALKING_TO_LIGHTNINGD, "Writing enable command");
/* We get a very simple response, ending in \n\n. */
memset(rbuf, 0, sizeof(rbuf));
while (!strends(rbuf, "\n\n")) {
size_t len = strlen(rbuf);
if (read(fd, rbuf + len, sizeof(rbuf) - len) < 0)
err(ERROR_TALKING_TO_LIGHTNINGD,
"Reading enable response");
}
}
static char *opt_set_level(const char *arg, enum log_level *level)
{
if (streq(arg, "none"))
*level = LOG_LEVEL_MAX + 1;
else if (!log_level_parse(arg, strlen(arg), level))
return "Invalid level";
return NULL;
}
static void opt_show_level(char buf[OPT_SHOW_LEN], const enum log_level *level)
{
if (*level == LOG_LEVEL_MAX + 1)
strncpy(buf, "none", OPT_SHOW_LEN-1);
else
strncpy(buf, log_level_name(*level), OPT_SHOW_LEN-1);
}
int main(int argc, char *argv[])
{
setup_locale();
@@ -487,6 +617,8 @@ int main(int argc, char *argv[])
int parserr;
enum format format = DEFAULT_FORMAT;
enum input input = DEFAULT_INPUT;
enum log_level notification_level = LOG_INFORM;
bool last_was_progress = false;
char *command = NULL;
err_set_progname(argv[0]);
@@ -514,6 +646,9 @@ int main(int argc, char *argv[])
"Use format key=value for <params>");
opt_register_noarg("-o|--order", opt_set_ordered, &input,
"Use params in order for <params>");
opt_register_arg("-N|--notifications", opt_set_level,
opt_show_level, &notification_level,
"Set notification level, or none");
opt_register_version();
@@ -567,6 +702,10 @@ int main(int argc, char *argv[])
"Connecting to '%s'", rpc_filename);
idstr = tal_fmt(ctx, "lightning-cli-%i", getpid());
if (notification_level <= LOG_LEVEL_MAX)
enable_notifications(fd);
cmd = tal_fmt(ctx,
"{ \"jsonrpc\" : \"2.0\", \"method\" : \"%s\", \"id\" : \"%s\", \"params\" :",
json_escape(ctx, method)->s, idstr);
@@ -607,6 +746,7 @@ int main(int argc, char *argv[])
/* Start with 1000 characters, 100 tokens. */
resp = tal_arr(ctx, char, 1000);
toks = tal_arr(ctx, jsmntok_t, 100);
toks[0].type = JSMN_UNDEFINED;
off = 0;
parserr = 0;
@@ -632,19 +772,34 @@ int main(int argc, char *argv[])
case JSMN_ERROR_INVAL:
errx(ERROR_TALKING_TO_LIGHTNINGD,
"Malformed response '%s'", resp);
case JSMN_ERROR_NOMEM: {
case JSMN_ERROR_NOMEM:
/* Need more tokens, double it */
if (!tal_resize(&toks, tal_count(toks) * 2))
oom_dump(fd, resp, off);
break;
}
case JSMN_ERROR_PART:
/* Need more data: make room if necessary */
if (off == tal_bytelen(resp) - 1) {
if (!tal_resize(&resp, tal_count(resp) * 2))
oom_dump(fd, resp, off);
/* We may actually have a complete token! */
if (toks[0].type == JSMN_UNDEFINED || toks[0].end == -1) {
/* Need more data: make room if necessary */
if (off == tal_bytelen(resp) - 1) {
if (!tal_resize(&resp, tal_count(resp) * 2))
oom_dump(fd, resp, off);
}
break;
}
/* Otherwise fall through... */
default:
if (handle_notify(resp, toks, notification_level,
&last_was_progress)) {
/* +2 for \n\n */
size_t len = toks[0].end - toks[0].start + 2;
memmove(resp, resp + len, off - len);
off -= len;
jsmn_init(&parser);
toks[0].type = JSMN_UNDEFINED;
/* Don't force another read! */
parserr = JSMN_ERROR_NOMEM;
}
break;
}
}
@@ -652,7 +807,10 @@ int main(int argc, char *argv[])
errx(ERROR_TALKING_TO_LIGHTNINGD,
"Non-object response '%s'", resp);
/* This can rellocate toks, so call before getting pointers to tokens */
if (last_was_progress)
printf("\n");
/* This can reallocate toks, so call before getting pointers to tokens */
format = choose_format(resp, &toks, method, command, format);
result = json_get_member(resp, toks, "result");
error = json_get_member(resp, toks, "error");