]> git.ipfire.org Git - people/stevee/pakfire.git/commitdiff
libpakfire: Add a simple progress bar
authorMichael Tremer <michael.tremer@ipfire.org>
Wed, 31 Mar 2021 20:33:35 +0000 (20:33 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Wed, 31 Mar 2021 20:33:35 +0000 (20:33 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
.gitignore
Makefile.am
src/libpakfire/include/pakfire/progressbar.h [new file with mode: 0644]
src/libpakfire/include/pakfire/util.h
src/libpakfire/progressbar.c [new file with mode: 0644]
src/libpakfire/util.c
tests/libpakfire/progressbar.c [new file with mode: 0644]
tests/testsuite.h

index db23c3ca6e4aa437cea976b265f20e3f0762b77a..8a58e7ac87451787c1b338f5d9744094d0c37fee 100644 (file)
@@ -22,6 +22,7 @@
 /tests/libpakfire/makefile
 /tests/libpakfire/packager
 /tests/libpakfire/parser
+/tests/libpakfire/progressbar
 /tests/libpakfire/repo
 /tests/libpakfire/util
 /tmp
index 632a9ad2d54186badf47c4ff233ba68d3bdfb7f8..4320cd4be23573fa0bd8ad013e03e478724c3b0f 100644 (file)
@@ -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 (file)
index 0000000..b55f0f2
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+#############################################################################*/
+
+#ifndef PAKFIRE_PROGRESSBAR_H
+#define PAKFIRE_PROGRESSBAR_H
+
+#ifdef PAKFIRE_PRIVATE
+
+#include <stdio.h>
+
+#include <pakfire/types.h>
+
+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 */
index 27213e85722f0f8498a4b1b9f72da9e3ac8468a6..5974f3800882e276a948cb7a7d4de4a614ab1891 100644 (file)
@@ -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 (file)
index 0000000..c6f321b
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+#############################################################################*/
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <time.h>
+
+#include <pakfire/logging.h>
+#include <pakfire/pakfire.h>
+#include <pakfire/progressbar.h>
+#include <pakfire/types.h>
+#include <pakfire/util.h>
+
+#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;
+}
index 6f9912dd478ad03d3a0b2328508de95fb5bf7070..18f865d97f0b864b278bfd4379cbb08d4f69752c 100644 (file)
@@ -42,6 +42,8 @@
 #include <pakfire/private.h>
 #include <pakfire/types.h>
 
+#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 (file)
index 0000000..b82d954
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+#############################################################################*/
+
+#include <errno.h>
+#include <stddef.h>
+#include <unistd.h>
+
+#include <pakfire/progressbar.h>
+
+#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();
+}
index d6a7b073d33f8bda33f46fdcce1070e5b960704a..c6d01c5fba38a85d517a7188c6df2e4bd8faf3b9 100644 (file)
@@ -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) { \