diff options
-rw-r--r-- | src/basic/glyph-util.c | 4 | ||||
-rw-r--r-- | src/basic/glyph-util.h | 2 | ||||
-rw-r--r-- | src/import/importctl.c | 91 | ||||
-rw-r--r-- | src/import/importd.c | 33 | ||||
-rw-r--r-- | src/shared/pretty-print.c | 72 | ||||
-rw-r--r-- | src/shared/pretty-print.h | 3 | ||||
-rw-r--r-- | src/test/meson.build | 4 | ||||
-rw-r--r-- | src/test/test-locale-util.c | 2 | ||||
-rw-r--r-- | src/test/test-progress-bar.c | 34 |
9 files changed, 228 insertions, 17 deletions
diff --git a/src/basic/glyph-util.c b/src/basic/glyph-util.c index b6b0f40ca6..d37be3234a 100644 --- a/src/basic/glyph-util.c +++ b/src/basic/glyph-util.c @@ -41,6 +41,8 @@ const char *special_glyph_full(SpecialGlyph code, bool force_utf) { [SPECIAL_GLYPH_TREE_SPACE] = " ", [SPECIAL_GLYPH_TREE_TOP] = ",-", [SPECIAL_GLYPH_VERTICAL_DOTTED] = ":", + [SPECIAL_GLYPH_HORIZONTAL_DOTTED] = "-", + [SPECIAL_GLYPH_HORIZONTAL_FAT] = "=", [SPECIAL_GLYPH_TRIANGULAR_BULLET] = ">", [SPECIAL_GLYPH_BLACK_CIRCLE] = "*", [SPECIAL_GLYPH_WHITE_CIRCLE] = "*", @@ -91,6 +93,8 @@ const char *special_glyph_full(SpecialGlyph code, bool force_utf) { /* Single glyphs in both cases */ [SPECIAL_GLYPH_VERTICAL_DOTTED] = u8"┆", + [SPECIAL_GLYPH_HORIZONTAL_DOTTED] = u8"┄", + [SPECIAL_GLYPH_HORIZONTAL_FAT] = u8"━", [SPECIAL_GLYPH_TRIANGULAR_BULLET] = u8"‣", [SPECIAL_GLYPH_BLACK_CIRCLE] = u8"●", [SPECIAL_GLYPH_WHITE_CIRCLE] = u8"○", diff --git a/src/basic/glyph-util.h b/src/basic/glyph-util.h index 2f70b187fc..db8dbbff26 100644 --- a/src/basic/glyph-util.h +++ b/src/basic/glyph-util.h @@ -13,6 +13,8 @@ typedef enum SpecialGlyph { SPECIAL_GLYPH_TREE_SPACE, SPECIAL_GLYPH_TREE_TOP, SPECIAL_GLYPH_VERTICAL_DOTTED, + SPECIAL_GLYPH_HORIZONTAL_DOTTED, + SPECIAL_GLYPH_HORIZONTAL_FAT, SPECIAL_GLYPH_TRIANGULAR_BULLET, SPECIAL_GLYPH_BLACK_CIRCLE, SPECIAL_GLYPH_WHITE_CIRCLE, diff --git a/src/import/importctl.c b/src/import/importctl.c index 688e583d07..7ada0e51df 100644 --- a/src/import/importctl.c +++ b/src/import/importctl.c @@ -45,6 +45,8 @@ static const char* arg_format = NULL; static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; static ImageClass arg_image_class = _IMAGE_CLASS_INVALID; +#define PROGRESS_PREFIX "Total: " + static int settle_image_class(void) { if (arg_image_class < 0) { @@ -68,13 +70,21 @@ static int settle_image_class(void) { return 0; } +typedef struct Context { + const char *object_path; + double progress; +} Context; + static int match_log_message(sd_bus_message *m, void *userdata, sd_bus_error *error) { - const char **our_path = userdata, *line; + Context *c = ASSERT_PTR(userdata); + const char *line; unsigned priority; int r; assert(m); - assert(our_path); + + if (!streq_ptr(c->object_path, sd_bus_message_get_path(m))) + return 0; r = sd_bus_message_read(m, "us", &priority, &line); if (r < 0) { @@ -82,23 +92,51 @@ static int match_log_message(sd_bus_message *m, void *userdata, sd_bus_error *er return 0; } - if (!streq_ptr(*our_path, sd_bus_message_get_path(m))) - return 0; - if (arg_quiet && LOG_PRI(priority) >= LOG_INFO) return 0; + if (!arg_quiet) + clear_progress_bar(PROGRESS_PREFIX); + log_full(priority, "%s", line); + + if (!arg_quiet) + draw_progress_bar(PROGRESS_PREFIX, c->progress * 100); + + return 0; +} + +static int match_progress_update(sd_bus_message *m, void *userdata, sd_bus_error *error) { + Context *c = ASSERT_PTR(userdata); + int r; + + assert(m); + + if (!streq_ptr(c->object_path, sd_bus_message_get_path(m))) + return 0; + + r = sd_bus_message_read(m, "d", &c->progress); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + if (!arg_quiet) + draw_progress_bar(PROGRESS_PREFIX, c->progress * 100); + return 0; } static int match_transfer_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { - const char **our_path = userdata, *path, *result; + Context *c = ASSERT_PTR(userdata); + const char *path, *result; uint32_t id; int r; assert(m); - assert(our_path); + + if (!arg_quiet) + clear_progress_bar(PROGRESS_PREFIX); r = sd_bus_message_read(m, "uos", &id, &path, &result); if (r < 0) { @@ -106,7 +144,7 @@ static int match_transfer_removed(sd_bus_message *m, void *userdata, sd_bus_erro return 0; } - if (!streq_ptr(*our_path, path)) + if (!streq_ptr(c->object_path, path)) return 0; sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), !streq_ptr(result, "done")); @@ -118,6 +156,9 @@ static int transfer_signal_handler(sd_event_source *s, const struct signalfd_sig assert(si); if (!arg_quiet) + clear_progress_bar(PROGRESS_PREFIX); + + if (!arg_quiet) log_info("Continuing download in the background. Use \"%s cancel-transfer %" PRIu32 "\" to abort transfer.", program_invocation_short_name, PTR_TO_UINT32(userdata)); @@ -127,11 +168,11 @@ static int transfer_signal_handler(sd_event_source *s, const struct signalfd_sig } static int transfer_image_common(sd_bus *bus, sd_bus_message *m) { - _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot_job_removed = NULL, *slot_log_message = NULL; + _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot_job_removed = NULL, *slot_log_message = NULL, *slot_progress_update = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_event_unrefp) sd_event* event = NULL; - const char *path = NULL; + Context c = {}; uint32_t id; int r; @@ -153,7 +194,9 @@ static int transfer_image_common(sd_bus *bus, sd_bus_message *m) { &slot_job_removed, bus_import_mgr, "TransferRemoved", - match_transfer_removed, NULL, &path); + match_transfer_removed, + /* add_callback= */ NULL, + &c); if (r < 0) return log_error_errno(r, "Failed to request match: %m"); @@ -161,10 +204,25 @@ static int transfer_image_common(sd_bus *bus, sd_bus_message *m) { bus, &slot_log_message, "org.freedesktop.import1", - NULL, + /* object_path= */ NULL, "org.freedesktop.import1.Transfer", "LogMessage", - match_log_message, NULL, &path); + match_log_message, + /* add_callback= */ NULL, + &c); + if (r < 0) + return log_error_errno(r, "Failed to request match: %m"); + + r = sd_bus_match_signal_async( + bus, + &slot_progress_update, + "org.freedesktop.import1", + /* object_path= */ NULL, + "org.freedesktop.import1.Transfer", + "ProgressUpdate", + match_progress_update, + /* add_callback= */ NULL, + &c); if (r < 0) return log_error_errno(r, "Failed to request match: %m"); @@ -172,12 +230,15 @@ static int transfer_image_common(sd_bus *bus, sd_bus_message *m) { if (r < 0) return log_error_errno(r, "Failed to transfer image: %s", bus_error_message(&error, r)); - r = sd_bus_message_read(reply, "uo", &id, &path); + r = sd_bus_message_read(reply, "uo", &id, &c.object_path); if (r < 0) return bus_log_parse_error(r); - if (!arg_quiet) + if (!arg_quiet) { + clear_progress_bar(PROGRESS_PREFIX); log_info("Enqueued transfer job %u. Press C-c to continue download in background.", id); + draw_progress_bar(PROGRESS_PREFIX, c.progress); + } (void) sd_event_add_signal(event, NULL, SIGINT|SD_EVENT_SIGNAL_PROCMASK, transfer_signal_handler, UINT32_TO_PTR(id)); (void) sd_event_add_signal(event, NULL, SIGTERM|SD_EVENT_SIGNAL_PROCMASK, transfer_signal_handler, UINT32_TO_PTR(id)); diff --git a/src/import/importd.c b/src/import/importd.c index 5aeb0b1895..493e1991b2 100644 --- a/src/import/importd.c +++ b/src/import/importd.c @@ -83,6 +83,7 @@ struct Transfer { unsigned n_canceled; unsigned progress_percent; + unsigned progress_percent_sent; int stdin_fd; int stdout_fd; @@ -166,7 +167,8 @@ static int transfer_new(Manager *m, Transfer **ret) { .stdin_fd = -EBADF, .stdout_fd = -EBADF, .verify = _IMPORT_VERIFY_INVALID, - .progress_percent= UINT_MAX, + .progress_percent = UINT_MAX, + .progress_percent_sent = UINT_MAX, }; id = m->current_transfer_id + 1; @@ -217,7 +219,28 @@ static void transfer_send_log_line(Transfer *t, const char *line) { line); if (r < 0) log_warning_errno(r, "Cannot emit log message signal, ignoring: %m"); - } +} + +static void transfer_send_progress_update(Transfer *t) { + int r; + + assert(t); + + if (t->progress_percent_sent == t->progress_percent) + return; + + r = sd_bus_emit_signal( + t->manager->bus, + t->object_path, + "org.freedesktop.import1.Transfer", + "ProgressUpdate", + "d", + transfer_percent_as_double(t)); + if (r < 0) + log_warning_errno(r, "Cannot emit progress update signal, ignoring: %m"); + + t->progress_percent_sent = t->progress_percent; +} static void transfer_send_logs(Transfer *t, bool flush) { assert(t); @@ -635,6 +658,8 @@ static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void t->progress_percent = (unsigned) r; log_debug("Got percentage from client: %u%%", t->progress_percent); + + transfer_send_progress_update(t); return 0; } @@ -1369,6 +1394,10 @@ static const sd_bus_vtable transfer_vtable[] = { SD_BUS_PARAM(priority) SD_BUS_PARAM(line), 0), + SD_BUS_SIGNAL_WITH_NAMES("ProgressUpdate", + "d", + SD_BUS_PARAM(progress), + 0), SD_BUS_VTABLE_END, }; diff --git a/src/shared/pretty-print.c b/src/shared/pretty-print.c index a4e5809446..543a9ebdcb 100644 --- a/src/shared/pretty-print.c +++ b/src/shared/pretty-print.c @@ -462,3 +462,75 @@ int terminal_tint_color(double hue, char **ret) { return 0; } + +void draw_progress_bar(const char *prefix, double percentage) { + + fputs("\r", stderr); + if (prefix) + fputs(prefix, stderr); + + if (!terminal_is_dumb()) { + size_t cols = columns(); + size_t prefix_length = strlen_ptr(prefix); + size_t length = cols > prefix_length + 6 ? cols - prefix_length - 6 : 0; + + fputs(ansi_highlight_green(), stderr); + + if (length > 5 && percentage >= 0.0 && percentage <= 100.0) { + size_t p = (size_t) (length * percentage / 100.0); + bool separator_done = false; + + for (size_t i = 0; i < length; i++) { + + if (i <= p) { + if (get_color_mode() == COLOR_24BIT) { + uint8_t r8, g8, b8; + double z = i == 0 ? 0 : (((double) i / p) * 100); + hsv_to_rgb(145 /* green */, z, 33 + z*2/3, &r8, &g8, &b8); + fprintf(stderr, "\x1B[38;2;%u;%u;%um", r8, g8, b8); + } + + fputs(special_glyph(SPECIAL_GLYPH_HORIZONTAL_FAT), stderr); + } else if (i+1 < length && !separator_done) { + fputs(ansi_normal(), stderr); + fputc(' ', stderr); + separator_done = true; + fputs(ansi_grey(), stderr); + } else + fputs(special_glyph(SPECIAL_GLYPH_HORIZONTAL_DOTTED), stderr); + } + + fputs(ansi_normal(), stderr); + fputc(' ', stderr); + } + } + + fprintf(stderr, + "%s%3.0f%%%s", + ansi_highlight(), + percentage, + ansi_normal()); + + if (!terminal_is_dumb()) + fputs(ANSI_ERASE_TO_END_OF_LINE, stderr); + + fputc('\r', stderr); + fflush(stderr); +} + +void clear_progress_bar(const char *prefix) { + + fputc('\r', stderr); + + if (terminal_is_dumb()) { + size_t l = strlen_ptr(prefix); + for (size_t i = 0; i < l; i ++) + fputc(' ', stderr); + + fputs(" ", stderr); + } else + fputs(ANSI_ERASE_TO_END_OF_LINE, stderr); + + fputc('\r', stderr); + fflush(stderr); +} diff --git a/src/shared/pretty-print.h b/src/shared/pretty-print.h index 6069318be7..940d34775b 100644 --- a/src/shared/pretty-print.h +++ b/src/shared/pretty-print.h @@ -49,3 +49,6 @@ static inline const char *green_check_mark_internal(char buffer[static GREEN_CHE #define COLOR_MARK_BOOL(b) ((b) ? GREEN_CHECK_MARK() : RED_CROSS_MARK()) int terminal_tint_color(double hue, char **ret); + +void draw_progress_bar(const char *prefix, double percentage); +void clear_progress_bar(const char *prefix); diff --git a/src/test/meson.build b/src/test/meson.build index c628eaa7db..a0f3882480 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -381,6 +381,10 @@ executables += [ 'dependencies' : threads, }, test_template + { + 'sources' : files('test-progress-bar.c'), + 'type' : 'manual', + }, + test_template + { 'sources' : files('test-qrcode-util.c'), 'dependencies' : libdl, }, diff --git a/src/test/test-locale-util.c b/src/test/test-locale-util.c index 67d9c7e65c..ab2d1f5746 100644 --- a/src/test/test-locale-util.c +++ b/src/test/test-locale-util.c @@ -92,6 +92,8 @@ TEST(dump_special_glyphs) { dump_glyph(SPECIAL_GLYPH_TREE_SPACE); dump_glyph(SPECIAL_GLYPH_TREE_TOP); dump_glyph(SPECIAL_GLYPH_VERTICAL_DOTTED); + dump_glyph(SPECIAL_GLYPH_HORIZONTAL_DOTTED); + dump_glyph(SPECIAL_GLYPH_HORIZONTAL_FAT); dump_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET); dump_glyph(SPECIAL_GLYPH_BLACK_CIRCLE); dump_glyph(SPECIAL_GLYPH_WHITE_CIRCLE); diff --git a/src/test/test-progress-bar.c b/src/test/test-progress-bar.c new file mode 100644 index 0000000000..b47adf0c28 --- /dev/null +++ b/src/test/test-progress-bar.c @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "pretty-print.h" +#include "random-util.h" +#include "tests.h" + +#define PROGRESS_PREFIX "test: " + +TEST(progress_bar) { + + draw_progress_bar(PROGRESS_PREFIX, 0); + + bool paused = false; + + for (double d = 0; d <= 100; d += 0.5) { + usleep_safe(random_u64_range(20 * USEC_PER_MSEC)); + draw_progress_bar(PROGRESS_PREFIX, d); + + if (!paused && d >= 50) { + clear_progress_bar(PROGRESS_PREFIX); + fputs("Sleeping for 1s...", stdout); + fflush(stdout); + usleep_safe(USEC_PER_SEC); + paused = true; + } + } + + draw_progress_bar(PROGRESS_PREFIX, 100); + usleep_safe(300 * MSEC_PER_SEC); + clear_progress_bar(PROGRESS_PREFIX); + fputs("Done.\n", stdout); +} + +DEFINE_TEST_MAIN(LOG_INFO); |