summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/basic/glyph-util.c4
-rw-r--r--src/basic/glyph-util.h2
-rw-r--r--src/import/importctl.c91
-rw-r--r--src/import/importd.c33
-rw-r--r--src/shared/pretty-print.c72
-rw-r--r--src/shared/pretty-print.h3
-rw-r--r--src/test/meson.build4
-rw-r--r--src/test/test-locale-util.c2
-rw-r--r--src/test/test-progress-bar.c34
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);