From: Michael Tremer Date: Wed, 31 Mar 2021 20:33:35 +0000 (+0000) Subject: libpakfire: Add a simple progress bar X-Git-Tag: 0.9.28~1285^2~448 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=563e96005a9020fb8a2b7122d2a3ed819c38c098;p=pakfire.git libpakfire: Add a simple progress bar Signed-off-by: Michael Tremer --- diff --git a/.gitignore b/.gitignore index db23c3ca6..8a58e7ac8 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ /tests/libpakfire/makefile /tests/libpakfire/packager /tests/libpakfire/parser +/tests/libpakfire/progressbar /tests/libpakfire/repo /tests/libpakfire/util /tmp diff --git a/Makefile.am b/Makefile.am index 632a9ad2d..4320cd4be 100644 --- a/Makefile.am +++ b/Makefile.am @@ -274,6 +274,7 @@ libpakfire_la_SOURCES = \ src/libpakfire/pakfire.c \ src/libpakfire/parser.c \ src/libpakfire/problem.c \ + src/libpakfire/progressbar.c \ src/libpakfire/pwd.c \ src/libpakfire/relation.c \ src/libpakfire/relationlist.c \ @@ -311,6 +312,7 @@ pkginclude_HEADERS += \ src/libpakfire/include/pakfire/parser.h \ src/libpakfire/include/pakfire/private.h \ src/libpakfire/include/pakfire/problem.h \ + src/libpakfire/include/pakfire/progressbar.h \ src/libpakfire/include/pakfire/pwd.h \ src/libpakfire/include/pakfire/relation.h \ src/libpakfire/include/pakfire/relationlist.h \ @@ -382,6 +384,7 @@ check_PROGRAMS += \ tests/libpakfire/makefile \ tests/libpakfire/packager \ tests/libpakfire/parser \ + tests/libpakfire/progressbar \ tests/libpakfire/repo \ tests/libpakfire/util @@ -527,6 +530,23 @@ tests_libpakfire_parser_LDADD = \ $(TESTSUITE_LDADD) \ $(PAKFIRE_LIBS) +dist_tests_libpakfire_progressbar_SOURCES = \ + tests/libpakfire/progressbar.c \ + src/libpakfire/progressbar.c \ + src/libpakfire/util.c + +tests_libpakfire_progressbar_CPPFLAGS = \ + $(TESTSUITE_CPPFLAGS) \ + $(JSON_C_CFLAGS) \ + -DPAKFIRE_PRIVATE + +tests_libpakfire_progressbar_LDADD = \ + $(TESTSUITE_LDADD) \ + $(PAKFIRE_LIBS) \ + $(ARCHIVE_LIBS) \ + $(JSON_C_LIBS) \ + $(UUID_LIBS) + dist_tests_libpakfire_repo_SOURCES = \ tests/libpakfire/repo.c diff --git a/src/libpakfire/include/pakfire/progressbar.h b/src/libpakfire/include/pakfire/progressbar.h new file mode 100644 index 000000000..b55f0f259 --- /dev/null +++ b/src/libpakfire/include/pakfire/progressbar.h @@ -0,0 +1,45 @@ +/*############################################################################# +# # +# Pakfire - The IPFire package management system # +# Copyright (C) 2021 Pakfire development team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +#############################################################################*/ + +#ifndef PAKFIRE_PROGRESSBAR_H +#define PAKFIRE_PROGRESSBAR_H + +#ifdef PAKFIRE_PRIVATE + +#include + +#include + +struct pakfire_progressbar; + +int pakfire_progressbar_create( + struct pakfire_progressbar** progressbar, Pakfire pakfire, FILE* f); + +struct pakfire_progressbar* pakfire_progressbar_ref(struct pakfire_progressbar* p); +struct pakfire_progressbar* pakfire_progressbar_unref(struct pakfire_progressbar* p); + +int pakfire_progressbar_start(struct pakfire_progressbar* p, unsigned long value); +int pakfire_progressbar_update(struct pakfire_progressbar* p, unsigned long value); +int pakfire_progressbar_increment(struct pakfire_progressbar* p); +int pakfire_progressbar_finish(struct pakfire_progressbar* p); + +#endif + +#endif /* PAKFIRE_PROGRESSBAR_H */ diff --git a/src/libpakfire/include/pakfire/util.h b/src/libpakfire/include/pakfire/util.h index 27213e857..5974f3800 100644 --- a/src/libpakfire/include/pakfire/util.h +++ b/src/libpakfire/include/pakfire/util.h @@ -81,6 +81,14 @@ int pakfire_archive_copy_data(struct archive* src, struct archive* dst, struct json_object* pakfire_json_parse_from_file(Pakfire pakfire, const char* path); +// Time Stuff + +struct timespec timespec_add(const struct timespec* t1, const struct timespec* t2); + +struct timespec timespec_from_ms(int milliseconds); + +int timespec_lt(struct timespec* t1, struct timespec* t2); + #endif #endif /* PAKFIRE_UTIL_H */ diff --git a/src/libpakfire/progressbar.c b/src/libpakfire/progressbar.c new file mode 100644 index 000000000..c6f321b00 --- /dev/null +++ b/src/libpakfire/progressbar.c @@ -0,0 +1,233 @@ +/*############################################################################# +# # +# Pakfire - The IPFire package management system # +# Copyright (C) 2021 Pakfire development team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +#############################################################################*/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define REDRAW_TIMEOUT 100 + +struct pakfire_progressbar { + Pakfire pakfire; + int nrefs; + + enum pakfire_progressbar_status { + PAKFIRE_PROGRESSBAR_INIT = 0, + PAKFIRE_PROGRESSBAR_RUNNING, + PAKFIRE_PROGRESSBAR_FINISHED, + } status; + + // The progress + unsigned long value; + unsigned long value_max; + unsigned long value_redraw; + unsigned long update_interval; + + // Store terminal settings globally + struct pakfire_terminal { + FILE* f; + int rows; + int cols; + } terminal; + + struct timespec time_start; + struct timespec time_redraw; +}; + +static int pakfire_progressbar_update_terminal_size(struct pakfire_progressbar* p) { + struct winsize w; + + int r = ioctl(fileno(p->terminal.f), TIOCGWINSZ, &w); + if (r) { + ERROR(p->pakfire, "Could not determine terminal size: %s\n", strerror(errno)); + } + + // Save result + p->terminal.rows = w.ws_row; + p->terminal.cols = w.ws_col; + + return 0; +} + +static void pakfire_progressbar_free(struct pakfire_progressbar* p) { + pakfire_unref(p->pakfire); + free(p); +} + +int pakfire_progressbar_create(struct pakfire_progressbar** progressbar, + Pakfire pakfire, FILE* f) { + struct pakfire_progressbar* p = calloc(1, sizeof(*p)); + if (!p) + return ENOMEM; + + p->pakfire = pakfire_ref(pakfire); + p->nrefs = 1; + + // Save output file + if (f) + p->terminal.f = f; + else + p->terminal.f = stderr; + + // Determine terminal size + int r = pakfire_progressbar_update_terminal_size(p); + if (r) + goto ERROR; + + // Done + *progressbar = p; + return 0; + +ERROR: + pakfire_progressbar_free(p); + + return r; +} + +struct pakfire_progressbar* pakfire_progressbar_ref(struct pakfire_progressbar* p) { + ++p->nrefs; + + return p; +} + +struct pakfire_progressbar* pakfire_progressbar_unref(struct pakfire_progressbar* p) { + if (--p->nrefs > 0) + return p; + + pakfire_progressbar_free(p); + return NULL; +} + +int pakfire_progressbar_start(struct pakfire_progressbar* p, unsigned long value) { + if (p->status != PAKFIRE_PROGRESSBAR_INIT) + return EINVAL; + + // Set status + p->status = PAKFIRE_PROGRESSBAR_RUNNING; + + // Store maximum value + p->value_max = value; + + // Redraw immediately + p->value_redraw = 0; + + // Set start time + int r = clock_gettime(CLOCK_MONOTONIC, &p->time_start); + if (r) { + ERROR(p->pakfire, "Could not set start time: %s\n", strerror(errno)); + return r; + } + + // Calculate update interval + p->update_interval = p->value_max / p->terminal.cols; + + return pakfire_progressbar_update(p, 0); +} + +static int pakfire_progressbar_redraw(struct pakfire_progressbar* p); + +int pakfire_progressbar_update(struct pakfire_progressbar* p, unsigned long value) { + if (p->status == PAKFIRE_PROGRESSBAR_INIT) + return EINVAL; + + p->value = value; + + return pakfire_progressbar_redraw(p); +} + +int pakfire_progressbar_increment(struct pakfire_progressbar* p) { + return pakfire_progressbar_update(p, p->value + 1); +} + +int pakfire_progressbar_finish(struct pakfire_progressbar* p) { + if (p->status != PAKFIRE_PROGRESSBAR_RUNNING) + return EINVAL; + + // Set status + p->status = PAKFIRE_PROGRESSBAR_FINISHED; + + // Set to maximum value + int r = pakfire_progressbar_update(p, p->value_max); + if (r) + return r; + + // Finish line + r = fputs("\n", p->terminal.f); + if (r <= 0 || r == EOF) + return 1; + + return 0; +} + +/* + This functions determines whether this progressbar needs to be redrawn + and sets any markers to determine the next redraw. +*/ +static int pakfire_progressbar_needs_redraw(struct pakfire_progressbar* p) { + struct timespec now; + + // Fetch the current time + int r = clock_gettime(CLOCK_MONOTONIC, &now); + if (r) + return r; + + // Redraw when we surpassed the next redraw value + if (p->value >= p->value_redraw) + goto REDRAW; + + // Redraw when we hit the timeout + if (timespec_lt(&now, &p->time_redraw)) + goto REDRAW; + + // Redraw when we are finished + if (p->status == PAKFIRE_PROGRESSBAR_FINISHED) + goto REDRAW; + + // No need to redraw + return 0; + +REDRAW: + // Compute next redraw steps + p->value_redraw = p->value + p->update_interval; + + struct timespec timeout = timespec_from_ms(REDRAW_TIMEOUT); + p->time_redraw = timespec_add(&now, &timeout); + + return 1; +} + +static int pakfire_progressbar_redraw(struct pakfire_progressbar* p) { + // Return when we should not be redrawing + if (!pakfire_progressbar_needs_redraw(p)) + return 0; + + fprintf(p->terminal.f, "\r%lu/%lu", p->value, p->value_max); + + return 0; +} diff --git a/src/libpakfire/util.c b/src/libpakfire/util.c index 6f9912dd4..18f865d97 100644 --- a/src/libpakfire/util.c +++ b/src/libpakfire/util.c @@ -42,6 +42,8 @@ #include #include +#define NSEC_PER_SEC 1000000000 + PAKFIRE_EXPORT int pakfire_string_startswith(const char* s, const char* prefix) { return !strncmp(s, prefix, strlen(prefix)); } @@ -639,3 +641,45 @@ struct json_object* pakfire_json_parse_from_file(Pakfire pakfire, const char* pa return json; } + +// Time Stuff + +static void timespec_normalize(struct timespec* t) { + while (t->tv_nsec >= NSEC_PER_SEC) { + t->tv_sec++; + t->tv_nsec -= NSEC_PER_SEC; + } + + while (t->tv_nsec <= -NSEC_PER_SEC) { + t->tv_sec--; + t->tv_nsec += NSEC_PER_SEC; + } +} + +struct timespec timespec_add(const struct timespec* t1, const struct timespec* t2) { + struct timespec r = { + .tv_sec = t1->tv_sec + t2->tv_sec, + .tv_nsec = t2->tv_nsec + t2->tv_nsec, + }; + + // Correct any negative values + timespec_normalize(&r); + + return r; +} + +struct timespec timespec_from_ms(int milliseconds) { + struct timespec t = { + .tv_sec = (milliseconds / 1000), + .tv_nsec = (milliseconds % 1000) * 1000000, + }; + + return t; +} + +int timespec_lt(struct timespec* t1, struct timespec* t2) { + return ( + t1->tv_sec < t2->tv_sec || + (t1->tv_sec == t2->tv_sec && t1->tv_nsec < t2->tv_nsec) + ); +} diff --git a/tests/libpakfire/progressbar.c b/tests/libpakfire/progressbar.c new file mode 100644 index 000000000..b82d954d0 --- /dev/null +++ b/tests/libpakfire/progressbar.c @@ -0,0 +1,54 @@ +/*############################################################################# +# # +# Pakfire - The IPFire package management system # +# Copyright (C) 2021 Pakfire development team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +#############################################################################*/ + +#include +#include +#include + +#include + +#include "../testsuite.h" + +static int test_run(const struct test* t) { + struct pakfire_progressbar* p; + + ASSERT_SUCCESS(pakfire_progressbar_create(&p, t->pakfire, NULL)); + + ASSERT(pakfire_progressbar_finish(p) == EINVAL); + + ASSERT_SUCCESS(pakfire_progressbar_start(p, 1000)); + + for (unsigned int i = 0; i < 1000; i++) { + ASSERT_SUCCESS(pakfire_progressbar_increment(p)); + usleep(2500); + } + + ASSERT_SUCCESS(pakfire_progressbar_finish(p)); + + ASSERT_NULL(pakfire_progressbar_unref(p)); + + return EXIT_SUCCESS; +} + +int main(int argc, char** argv) { + testsuite_add_test(test_run); + + return testsuite_run(); +} diff --git a/tests/testsuite.h b/tests/testsuite.h index d6a7b073d..c6d01c5fb 100644 --- a/tests/testsuite.h +++ b/tests/testsuite.h @@ -63,6 +63,15 @@ int testsuite_run(); } \ } while (0) +#define ASSERT_NULL(expr) \ + do { \ + if (!((expr) == NULL)) { \ + LOG_ERROR("Failed assertion: " #expr " == NULL %s:%d %s\n", \ + __FILE__, __LINE__, __PRETTY_FUNCTION__); \ + return EXIT_FAILURE; \ + } \ + } while (0) + #define ASSERT_SUCCESS(expr) \ do { \ if ((expr)) { \ @@ -72,6 +81,21 @@ int testsuite_run(); } \ } while (0) +#define ASSERT_ERRNO(expr, e) \ + do { \ + if (!(expr)) { \ + LOG_ERROR("Failed assertion: " #expr " unexpectedly didn't fail in %s:%d %s\n", \ + __FILE__, __LINE__, __PRETTY_FUNCTION__); \ + return EXIT_FAILURE; \ + } \ + if (errno != e) { \ + LOG_ERROR("Failed assertion: " #expr " failed with (%d - %s) " \ + "but was expected to fail with (%d - %s) in %s:%d\n", \ + errno, strerror(errno), e, strerror(e), __FILE__, __LINE__); \ + return EXIT_FAILURE; \ + } \ + } while (0) + #define ASSERT_STRING_EQUALS(value, string) \ do { \ if (!value) { \