]> git.ipfire.org Git - pakfire.git/commitdiff
progress: Add a new abstraction to indicate progress
authorMichael Tremer <michael.tremer@ipfire.org>
Sun, 1 Oct 2023 10:31:25 +0000 (10:31 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Sun, 1 Oct 2023 10:31:25 +0000 (10:31 +0000)
The pakfire library has a progressbar object which is complicated to
handle when functions are called from Python, etc.

This new abstraction allows easy handling of progress inside the library
and other applications can hook into it and show the desired status in
whatever way they like.

This patch also implements a classic progressbar in the CLI utils.

Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
12 files changed:
Makefile.am
src/cli/lib/progressbar.c [new file with mode: 0644]
src/cli/lib/progressbar.h [new file with mode: 0644]
src/cli/lib/terminal.c
src/cli/lib/terminal.h
src/cli/pakfire-builder.c
src/libpakfire/compress.c
src/libpakfire/include/pakfire/pakfire.h
src/libpakfire/include/pakfire/progress.h [new file with mode: 0644]
src/libpakfire/libpakfire.sym
src/libpakfire/pakfire.c
src/libpakfire/progress.c [new file with mode: 0644]

index 5122c4ef784b586dafd249e7f892c21a4b6a5b8a..270339ba273002292a23481763381f5923fcacf3 100644 (file)
@@ -239,6 +239,7 @@ libpakfire_la_SOURCES = \
        src/libpakfire/pakfire.c \
        src/libpakfire/parser.c \
        src/libpakfire/problem.c \
+       src/libpakfire/progress.c \
        src/libpakfire/progressbar.c \
        src/libpakfire/pwd.c \
        src/libpakfire/repo.c \
@@ -279,6 +280,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/progress.h \
        src/libpakfire/include/pakfire/progressbar.h \
        src/libpakfire/include/pakfire/pwd.h \
        src/libpakfire/include/pakfire/repo.h \
@@ -450,6 +452,8 @@ libcli_la_SOURCES = \
        src/cli/lib/info.h \
        src/cli/lib/install.h \
        src/cli/lib/install.c \
+       src/cli/lib/progressbar.c \
+       src/cli/lib/progressbar.h \
        src/cli/lib/provides.h \
        src/cli/lib/provides.c \
        src/cli/lib/remove.c \
diff --git a/src/cli/lib/progressbar.c b/src/cli/lib/progressbar.c
new file mode 100644 (file)
index 0000000..ef7ff1e
--- /dev/null
@@ -0,0 +1,631 @@
+/*#############################################################################
+#                                                                             #
+# Pakfire - The IPFire package management system                              #
+# Copyright (C) 2023 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 <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <pakfire/i18n.h>
+#include <pakfire/pakfire.h>
+#include <pakfire/progress.h>
+
+#include "progressbar.h"
+#include "terminal.h"
+
+#define DRAWS_PER_SECOND 20
+
+static const struct timespec TIMER = {
+       .tv_sec  = 0,
+       .tv_nsec = 1000000000 / DRAWS_PER_SECOND,
+};
+
+struct cli_progressbar;
+
+struct cli_progressbar_widget {
+       STAILQ_ENTRY(cli_progressbar_widget) nodes;
+
+       int expandable;
+       void* data;
+
+       const char* (*print)(struct cli_progressbar* p,
+               struct cli_progressbar_widget* widget, unsigned int width, void* data);
+       void (*free)(void* data);
+
+       char buffer[1024];
+};
+
+struct cli_progressbar {
+       struct pakfire_progress* progress;
+
+       // Output
+       FILE* f;
+
+       // Reference to the thread that is drawing the progress bar
+       pthread_t renderer;
+       volatile unsigned int is_running;
+
+       // Widgets
+       STAILQ_HEAD(widgets, cli_progressbar_widget) widgets;
+       unsigned int num_widgets;
+};
+
+static int cli_progressbar_create(struct cli_progressbar** progressbar,
+               struct pakfire_progress* progress, FILE* f) {
+       struct cli_progressbar* p = NULL;
+
+       // Allocate some memory
+       p = calloc(1, sizeof(*p));
+       if (!p)
+               return -errno;
+
+       // Keep a reference to progress
+       p->progress = progress;
+
+       // Default writing to stdout
+       if (!f)
+               f = stdout;
+
+       // Where to write to?
+       p->f = f;
+
+       // Setup widgets
+       STAILQ_INIT(&p->widgets);
+
+       // Done
+       *progressbar = p;
+
+       return 0;
+}
+
+static void cli_progressbar_widget_free(struct cli_progressbar_widget* widget) {
+       // Call own free method
+       if (widget->free && widget->data)
+               widget->free(widget->data);
+
+       free(widget);
+}
+
+static void cli_progressbar_free_widgets(struct cli_progressbar* p) {
+       struct cli_progressbar_widget* widget = NULL;
+
+       // Free widgets
+       while (!STAILQ_EMPTY(&p->widgets)) {
+               widget = STAILQ_FIRST(&p->widgets);
+               STAILQ_REMOVE_HEAD(&p->widgets, nodes);
+
+               cli_progressbar_widget_free(widget);
+       }
+}
+
+static void cli_progressbar_free(struct cli_progressbar* p) {
+       cli_progressbar_free_widgets(p);
+       free(p);
+}
+
+static void __cli_progressbar_free(struct pakfire* pakfire,
+               struct pakfire_progress* progress, void* data) {
+       struct cli_progressbar* progressbar = data;
+
+       cli_progressbar_free(progressbar);
+}
+
+static int cli_progressbar_draw(struct cli_progressbar* p) {
+       struct cli_progressbar_widget* widget = NULL;
+       int rows;
+       int cols;
+       int r;
+
+       // Fetch terminal size
+       r = cli_term_get_dimensions(&rows, &cols);
+       if (r)
+               return r;
+
+       unsigned int cols_left = cols - p->num_widgets - 2;
+       unsigned int i = 0;
+
+       // Create an array with the result of all print functions
+       const char* elements[p->num_widgets];
+
+       // Reset all elements
+       for (i = 0; i < p->num_widgets; i++)
+               elements[i] = NULL;
+
+       // Reset i
+       i = 0;
+
+       // Process all non-expandable widgets in the first pass
+       STAILQ_FOREACH(widget, &p->widgets, nodes) {
+               const char* element = NULL;
+
+               if (!widget->expandable) {
+                       element = widget->print(p, widget, 0, widget->data);
+                       if (element)
+                               cols_left -= strlen(element);
+               }
+
+               elements[i++] = element;
+       }
+
+       // How many expandable widgets are left?
+       int num_expandables = p->num_widgets - i;
+
+       // How much space do we allocate to each of them?
+       int width = cols_left - num_expandables;
+
+       i = 0;
+
+       // Process all expandable widgets
+       STAILQ_FOREACH(widget, &p->widgets, nodes) {
+               const char* element = elements[i];
+
+               if (widget->expandable)
+                       element = widget->print(p, widget, width, widget->data);
+
+               elements[i++] = element;
+       }
+
+       // Reset the line
+       fputs("\r", p->f);
+
+       // Print all elements
+       for (i = 0; i < p->num_widgets; i++) {
+               // Skip anything that returned nothing
+               if (!elements[i])
+                       continue;
+
+               fputs(" ", p->f);
+               fputs(elements[i], p->f);
+       }
+
+       // Flush everything
+       fflush(p->f);
+
+       return 0;
+}
+
+static void* cli_progressbar_renderer(void* data) {
+       struct cli_progressbar* progressbar = data;
+       int r;
+
+       do {
+               // Redraw the progress bar
+               r = cli_progressbar_draw(progressbar);
+               if (r)
+                       goto ERROR;
+
+               // Sleep for a short moment
+               nanosleep(&TIMER, NULL);
+
+       // Keep doing this for as long as we are running
+       } while (progressbar->is_running);
+
+       // Perform a final draw
+       r = cli_progressbar_draw(progressbar);
+       if (r)
+               goto ERROR;
+
+       // Finish line
+       r = fputs("\n", progressbar->f);
+       if (r <= 0 || r == EOF) {
+               r = 1;
+               goto ERROR;
+       }
+
+       // Success
+       r = 0;
+
+ERROR:
+       return (void *)(intptr_t)r;
+}
+
+static int cli_progressbar_start(struct pakfire* pakfire, struct pakfire_progress* progress,
+               void* data, unsigned long int max_value) {
+       struct cli_progressbar* progressbar = data;
+
+       // Set as running
+       progressbar->is_running = 1;
+
+       // Launch the renderer thread
+       return pthread_create(&progressbar->renderer, NULL, cli_progressbar_renderer, progressbar);
+}
+
+static int cli_progressbar_finish(struct pakfire* pakfire, struct pakfire_progress* progress,
+               void* data) {
+       struct cli_progressbar* progressbar = data;
+       void* retval = NULL;
+       int r;
+
+       // We are no longer running
+       progressbar->is_running = 0;
+
+       // Wait until the render thread is done
+       if (progressbar->renderer) {
+               r = pthread_join(progressbar->renderer, &retval);
+               if (r)
+                       return r;
+       }
+
+       return (intptr_t)retval;
+}
+
+static int cli_progressbar_add_widget(struct cli_progressbar* p,
+               const char* (*print)(struct cli_progressbar* p,
+                       struct cli_progressbar_widget* widget, unsigned int width, void* data),
+               void (*free)(void* data), int expandable, void* data) {
+       // Allocate the widget
+       struct cli_progressbar_widget* widget = calloc(1, sizeof(*widget));
+       if (!widget)
+               return -ENOMEM;
+
+       // Assign everything
+       widget->print = print;
+       widget->free = free;
+       widget->expandable = expandable;
+       widget->data = data;
+
+       // Append it to the list
+       STAILQ_INSERT_TAIL(&p->widgets, widget, nodes);
+       p->num_widgets++;
+
+       return 0;
+}
+
+static const char* cli_progressbar_string(struct cli_progressbar* p,
+               struct cli_progressbar_widget* widget, unsigned int width, void* data) {
+       return (const char*)data;
+}
+
+static int cli_progressbar_add_string(struct cli_progressbar* p, const char* format, ...) {
+       char* s = NULL;
+       va_list args;
+
+       va_start(args, format);
+       int r = vasprintf(&s, format, args);
+       va_end(args);
+
+       if (r < 0)
+               return r;
+
+       return cli_progressbar_add_widget(p, cli_progressbar_string, free, 0, s);
+}
+
+static const char* cli_progressbar_counter(struct cli_progressbar* p,
+               struct cli_progressbar_widget* widget, unsigned int width, void* data) {
+       int r;
+
+       // Fetch progress
+       unsigned long int value = pakfire_progress_get_value(p->progress);
+       unsigned long int max_value = pakfire_progress_get_value(p->progress);
+
+       // Format the result
+       r = snprintf(widget->buffer, sizeof(widget->buffer), "%lu/%lu", value, max_value);
+       if (r < 0)
+               return NULL;
+
+       return widget->buffer;
+}
+
+static int cli_progressbar_add_counter(struct cli_progressbar* p) {
+       return cli_progressbar_add_widget(p, cli_progressbar_counter, NULL, 0, NULL);
+}
+
+static const char* cli_progressbar_percentage(struct cli_progressbar* p,
+               struct cli_progressbar_widget* widget, unsigned int width, void* data) {
+       int r;
+
+       // Fetch percentage
+       double percentage = pakfire_progress_get_percentage(p->progress);
+
+       // Format to string
+       r = snprintf(widget->buffer, sizeof(widget->buffer), "%3.0f%%", percentage);
+       if (r < 0)
+               return NULL;
+
+       return widget->buffer;
+}
+
+static int cli_progressbar_add_percentage(struct cli_progressbar* p) {
+       return cli_progressbar_add_widget(p, cli_progressbar_percentage, NULL, 0, NULL);
+}
+
+static const char* cli_progressbar_bar(struct cli_progressbar* p,
+               struct cli_progressbar_widget* widget, unsigned int width, void* data) {
+       if (width >= sizeof(widget->buffer) || width < 2)
+               return NULL;
+
+       // Remove the bar when we are finished so that the terminal is not so cluttered
+       if (!p->is_running) {
+               for (unsigned int i = 0; i < width; i++)
+                       widget->buffer[i] = ' ';
+               widget->buffer[width] = '\0';
+
+               return widget->buffer;
+       }
+
+       unsigned int fill = pakfire_progress_get_percentage(p->progress) * (width - 2) / 100;
+
+       // Write brackets
+       widget->buffer[0] = '[';
+       widget->buffer[width-1] = ']';
+
+       // Write bar
+       for (unsigned int i = 1; i < width - 1; i++) {
+               if (i <= fill)
+                       widget->buffer[i] = '#';
+               else
+                       widget->buffer[i] = '-';
+       }
+
+       // Terminat the string
+       widget->buffer[width] = '\0';
+
+       return widget->buffer;
+}
+
+static int cli_progressbar_add_bar(struct cli_progressbar* p) {
+       return cli_progressbar_add_widget(p, cli_progressbar_bar, NULL, 1, NULL);
+}
+
+static const char* cli_progressbar_elapsed_time(struct cli_progressbar* p,
+               struct cli_progressbar_widget* widget, unsigned int width, void* data) {
+       int r;
+
+       // Fetch the elapsed time
+       time_t t = pakfire_progress_get_elapsed_time(p->progress);
+       if (t < 0)
+               return NULL;
+
+       // Format the time
+       r = snprintf(widget->buffer, sizeof(widget->buffer), "%02lu:%02lu", t / 60, t % 60);
+       if (r)
+               return NULL;
+
+       return widget->buffer;
+}
+
+static int cli_progressbar_add_elapsed_time(struct cli_progressbar* p) {
+       return cli_progressbar_add_widget(p, cli_progressbar_elapsed_time, NULL, 0, NULL);
+}
+
+static const char* cli_progressbar_eta(struct cli_progressbar* p,
+               struct cli_progressbar_widget* widget, unsigned int width, void* data) {
+       int r;
+
+       // Show total time when finished
+       if (!p->is_running)
+               return cli_progressbar_elapsed_time(p, widget, width, data);
+
+       // Fetch the ETA
+       time_t t = pakfire_progress_get_eta(p->progress);
+
+       // Print a placeholder when we have no ETA (yet)
+       if (t <= 0) {
+               r = snprintf(widget->buffer, sizeof(widget->buffer), "%-5s: --:--:--", _("ETA"));
+               if (r < 0)
+                       return NULL;
+
+       // Otherwise show the ETA
+       } else {
+               r = snprintf(widget->buffer, sizeof(widget->buffer), "%-5s: %02lu:%02lu",
+                       _("ETA"), t / 60, t % 60);
+               if (r < 0)
+                       return NULL;
+       }
+
+       return widget->buffer;
+}
+
+static int cli_progressbar_add_eta(struct cli_progressbar* p) {
+       return cli_progressbar_add_widget(p, cli_progressbar_eta, NULL, 0, NULL);
+}
+
+#define cli_progressbar_format_speed(s, speed) \
+       __cli_progressbar_format_speed(s, sizeof(s), speed)
+
+static int __cli_progressbar_format_speed(char* s, const size_t length, double value) {
+       const char* units[] = {
+               "%4.0fB/s",
+               "%4.0fkB/s",
+               "%4.1fMB/s",
+               "%4.1fGB/s",
+               "%4.1fTB/s",
+               NULL
+       };
+       int r;
+
+       for (const char** unit = units; *unit; unit++) {
+               if (value >= 1024.0) {
+                       value /= 1024.0;
+                       continue;
+               }
+
+               // Format the string
+               r = snprintf(s, length, *unit, value);
+               if (r < 0)
+                       return r;
+
+               return 0;
+       }
+
+       return 1;
+}
+
+#define cli_progressbar_format_size(s, size) \
+       __cli_progressbar_format_size(s, sizeof(s), size)
+
+static int __cli_progressbar_format_size(char* s, const size_t length, double value) {
+       const char* units[] = {
+               "%.0f ",
+               "%.0fk",
+               "%.1fM",
+               "%.1fG",
+               "%.1fT",
+               NULL
+       };
+       int r;
+
+       for (const char** unit = units; *unit; unit++) {
+               if (value >= 1024.0) {
+                       value /= 1024.0;
+                       continue;
+               }
+
+               // Format the string
+               r = snprintf(s, length, *unit, value);
+               if (r < 0)
+                       return r;
+
+               return 0;
+       }
+
+       return 1;
+}
+
+static const char* cli_progressbar_bytes_transferred(struct cli_progressbar* p,
+               struct cli_progressbar_widget* widget, unsigned int width, void* data) {
+       char buffer[16];
+       int r;
+
+       // Fetch value
+       unsigned long int value = pakfire_progress_get_value(p->progress);
+
+       // Format the size
+       r = cli_progressbar_format_size(buffer, value);
+       if (r < 0)
+               return NULL;
+
+       // Add padding so that the string is always at least five characters long
+       r = snprintf(widget->buffer, sizeof(widget->buffer), "%-5s", buffer);
+       if (r < 0)
+               return NULL;
+
+       return widget->buffer;
+}
+
+static int cli_progressbar_add_bytes_transferred(struct cli_progressbar* p) {
+       return cli_progressbar_add_widget(p, cli_progressbar_bytes_transferred, NULL, 0, NULL);
+}
+
+static const char* cli_progressbar_transfer_speed(struct cli_progressbar* p,
+               struct cli_progressbar_widget* widget, unsigned int width, void* data) {
+       double speed = pakfire_progress_get_transfer_speed(p->progress);
+       if (speed < 0)
+               return NULL;
+
+       // Format the speed
+       int r = cli_progressbar_format_speed(widget->buffer, speed);
+       if (r < 0)
+                       return NULL;
+
+       return widget->buffer;
+}
+
+static int cli_progressbar_add_transfer_speed(struct cli_progressbar* p) {
+       return cli_progressbar_add_widget(p, cli_progressbar_transfer_speed, NULL, 0, NULL);
+}
+
+int cli_setup_progressbar(struct pakfire* pakfire, struct pakfire_progress* p, void* data) {
+       struct cli_progressbar* progressbar = NULL;
+       const char* title = NULL;
+       int r;
+
+       // Allocate a new progressbar
+       r = cli_progressbar_create(&progressbar, p, NULL);
+       if (r)
+               return r;
+
+       // Set callback data
+       pakfire_progress_set_callback_data(p, progressbar);
+
+       // Set start callback
+       pakfire_progress_set_start_callback(p, cli_progressbar_start);
+
+       // Set finish callback
+       pakfire_progress_set_finish_callback(p, cli_progressbar_finish);
+
+       // Set free callback
+       pakfire_progress_set_free_callback(p, __cli_progressbar_free);
+
+       // Add the title
+       title = pakfire_progress_get_title(p);
+       if (title) {
+               r = cli_progressbar_add_string(progressbar, "%s", title);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Show the bar
+       r = cli_progressbar_add_bar(progressbar);
+       if (r)
+               goto ERROR;
+
+       // Show bytes transferred
+       if (pakfire_progress_has_flag(p, PAKFIRE_PROGRESS_SHOW_BYTES_TRANSFERRED)) {
+               r = cli_progressbar_add_bytes_transferred(progressbar);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Show transfer speed
+       if (pakfire_progress_has_flag(p, PAKFIRE_PROGRESS_SHOW_TRANSFER_SPEED)) {
+               r = cli_progressbar_add_transfer_speed(progressbar);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Show elapsed time
+       if (pakfire_progress_has_flag(p, PAKFIRE_PROGRESS_SHOW_ELAPSED_TIME)) {
+               r = cli_progressbar_add_elapsed_time(progressbar);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Show ETA
+       if (pakfire_progress_has_flag(p, PAKFIRE_PROGRESS_SHOW_ETA)) {
+               r = cli_progressbar_add_eta(progressbar);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Show counter
+       if (pakfire_progress_has_flag(p, PAKFIRE_PROGRESS_SHOW_COUNTER)) {
+               r = cli_progressbar_add_counter(progressbar);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Show percentage
+       if (pakfire_progress_has_flag(p, PAKFIRE_PROGRESS_SHOW_PERCENTAGE)) {
+               r = cli_progressbar_add_percentage(progressbar);
+               if (r)
+                       goto ERROR;
+       }
+
+       return 0;
+
+ERROR:
+       if (progressbar)
+               cli_progressbar_free(progressbar);
+
+       return r;
+}
diff --git a/src/cli/lib/progressbar.h b/src/cli/lib/progressbar.h
new file mode 100644 (file)
index 0000000..caf27b3
--- /dev/null
@@ -0,0 +1,29 @@
+/*#############################################################################
+#                                                                             #
+# Pakfire - The IPFire package management system                              #
+# Copyright (C) 2023 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_CLI_PROGRESSBAR_H
+#define PAKFIRE_CLI_PROGRESSBAR_H
+
+#include <pakfire/pakfire.h>
+#include <pakfire/progress.h>
+
+int cli_setup_progressbar(struct pakfire* pakfire, struct pakfire_progress* p, void* data);
+
+#endif /* PAKFIRE_CLI_PROGRESSBAR_H */
index 53fbca07466225f29e46e3be5573a240a0806a33..046a00fccdf79b71612f08f3b3aa085f5291fdf0 100644 (file)
 #                                                                             #
 #############################################################################*/
 
+#include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <sys/ioctl.h>
 #include <unistd.h>
 
 #include <pakfire/i18n.h>
 
 #include "terminal.h"
 
+// Store terminal dimensions globally
+static struct cli_term_dimensions {
+       int rows;
+       int cols;
+       int __signal_registered;
+} dimensions;
+
+static void cli_term_update_dimensions(int signum) {
+       struct winsize w;
+       const int fd = STDOUT_FILENO;
+
+       // Set defaults
+       dimensions.rows = 80;
+       dimensions.cols = 20;
+
+       // Check if output file is a TTY
+       int r = isatty(fd);
+       if (r != 1)
+               return;
+
+       // Fetch the window size
+       r = ioctl(fd, TIOCGWINSZ, &w);
+       if (r)
+               return;
+
+       // Save result
+       dimensions.rows = w.ws_row;
+       dimensions.cols = w.ws_col;
+}
+
+int cli_term_get_dimensions(int* rows, int* cols) {
+       // Update data if not done, yet
+       if (!dimensions.rows || !dimensions.cols)
+               cli_term_update_dimensions(SIGWINCH);
+
+       // Register signal handler to update terminal size
+       if (!dimensions.__signal_registered) {
+               signal(SIGWINCH, cli_term_update_dimensions);
+
+               // Store that we have registered the signal
+               dimensions.__signal_registered = 1;
+       }
+
+       // Copy the dimensions
+       *rows = dimensions.rows;
+       *cols = dimensions.cols;
+
+       return 0;
+}
+
 int cli_term_is_interactive() {
        return isatty(STDIN_FILENO) && isatty(STDOUT_FILENO) && isatty(STDERR_FILENO);
 }
index 9798c102d32c1784675deb8c4a4d61391a65816e..f48b778067e3517b0ba3579e06c346c32898ca66 100644 (file)
@@ -24,6 +24,8 @@
 #include <pakfire/pakfire.h>
 #include <pakfire/transaction.h>
 
+int cli_term_get_dimensions(int* rows, int* cols);
+
 int cli_term_is_interactive(void);
 
 int cli_term_confirm(struct pakfire* pakfire,
index ea2603770b978f867f712f822f600b0d528c3cda..5888fd60d0665824a6b1616bfea292bf980fa8c9 100644 (file)
@@ -35,6 +35,7 @@
 #include "lib/dist.h"
 #include "lib/image.h"
 #include "lib/info.h"
+#include "lib/progressbar.h"
 #include "lib/provides.h"
 #include "lib/repo.h"
 #include "lib/repolist.h"
@@ -234,6 +235,9 @@ static int setup_pakfire(const struct config* config, struct pakfire** pakfire)
        if (r)
                goto ERROR;
 
+       // Setup progress callback
+       pakfire_set_setup_progress_callback(p, cli_setup_progressbar, NULL);
+
        // Enable repositories
        for (unsigned int i = 0; i < config->num_enable_repos; i++)
                cli_set_repo_enabled(p, config->enable_repos[i], 1);
index d6d8641f42848b466798c8ca24994b1d9d434fd3..d187ce9b73a1ab0511d46733013136a697f5fe90 100644 (file)
@@ -628,55 +628,18 @@ struct pakfire_extract {
        // The writer
        struct archive* writer;
 
-       // The progressbar
-       struct pakfire_progressbar* progressbar;
+       // The progress indicator
+       struct pakfire_progress* progress;
 };
 
-static int pakfire_extract_progressbar_create(struct pakfire_progressbar** progressbar,
-               const char* message, int flags) {
-       int r;
-
-       // Create the progressbar
-       r = pakfire_progressbar_create(progressbar, NULL);
-       if (r)
-               return r;
-
-       // Add message
-       if (message) {
-               r = pakfire_progressbar_add_string(*progressbar, "%s", message);
-               if (r)
-                       return r;
-       }
-
-       // Add bar
-       r = pakfire_progressbar_add_bar(*progressbar);
-       if (r)
-               return r;
-
-       // Add throughput
-       if (flags & PAKFIRE_EXTRACT_SHOW_THROUGHPUT) {
-               r = pakfire_progressbar_add_transfer_speed(*progressbar);
-               if (r)
-                       return r;
-       }
-
-       // Add percentage
-       r = pakfire_progressbar_add_percentage(*progressbar);
-       if (r)
-               return r;
-
-       // Success
-       return 0;
-}
-
 static void pakfire_extract_progress(void* p) {
        struct pakfire_extract* data = (struct pakfire_extract*)p;
 
        // Fetch how many bytes have been read
        const size_t position = archive_filter_bytes(data->archive, -1);
 
-       // Update progressbar
-       pakfire_progressbar_update(data->progressbar, position);
+       // Update progress
+       pakfire_progress_update(data->progress, position);
 }
 
 static int __pakfire_extract(struct pakfire* pakfire, struct archive* a,
@@ -817,6 +780,7 @@ int pakfire_extract(struct pakfire* pakfire, struct archive* archive,
                size_t size, struct pakfire_filelist* filelist,
                const char* prefix, const char* message,
                pakfire_walk_filter_callback filter_callback, int flags) {
+       int progress_flags = PAKFIRE_PROGRESS_SHOW_PERCENTAGE;
        int r = 1;
 
        // Use / if no prefix is set
@@ -835,9 +799,6 @@ int pakfire_extract(struct pakfire* pakfire, struct archive* archive,
        // Is this a dry run?
        const int dry_run = flags & PAKFIRE_EXTRACT_DRY_RUN;
 
-       // Should we show a progress bar?
-       const int no_progress = flags & PAKFIRE_EXTRACT_NO_PROGRESS;
-
        // Allocate writer
        if (!dry_run) {
                data.writer = pakfire_make_archive_disk_writer(pakfire, 1);
@@ -848,19 +809,27 @@ int pakfire_extract(struct pakfire* pakfire, struct archive* archive,
                }
        }
 
-       // Create the progressbar
-       if (!no_progress) {
-               r = pakfire_extract_progressbar_create(&data.progressbar, message, flags);
-               if (r)
-                       goto ERROR;
+       // Should we show any progress?
+       if (flags & PAKFIRE_EXTRACT_NO_PROGRESS)
+               progress_flags |= PAKFIRE_PROGRESS_NO_PROGRESS;
 
-               // Register progress callback
-               archive_read_extract_set_progress_callback(data.archive,
-                       pakfire_extract_progress, &data);
+       // Show throughput?
+       if (flags & PAKFIRE_EXTRACT_SHOW_THROUGHPUT)
+               progress_flags |= PAKFIRE_PROGRESS_SHOW_TRANSFER_SPEED;
 
-               // Start progressbar
-               pakfire_progressbar_start(data.progressbar, size);
-       }
+       // Create the progressbar indicator
+       r = pakfire_progress_create(&data.progress, pakfire, message, progress_flags);
+       if (r)
+               goto ERROR;
+
+       // Register progress callback
+       archive_read_extract_set_progress_callback(data.archive,
+               pakfire_extract_progress, &data);
+
+       // Start progress
+       r = pakfire_progress_start(data.progress, size);
+       if (r)
+               goto ERROR;
 
        // Walk through the entire archive
        r = pakfire_walk(pakfire, archive, __pakfire_extract, filter_callback, &data);
@@ -868,12 +837,13 @@ int pakfire_extract(struct pakfire* pakfire, struct archive* archive,
                goto ERROR;
 
        // Finish the progressbar
-       if (data.progressbar)
-               pakfire_progressbar_finish(data.progressbar);
+       r = pakfire_progress_finish(data.progress);
+       if (r)
+               goto ERROR;
 
 ERROR:
-       if (data.progressbar)
-               pakfire_progressbar_unref(data.progressbar);
+       if (data.progress)
+               pakfire_progress_unref(data.progress);
        if (data.writer)
                archive_write_free(data.writer);
 
index b06b2263a576a7b4f81d7b15ba8f6925c6f5b2fe..235479743d89a4cecbeb1a64896935266c9bc009 100644 (file)
@@ -33,6 +33,7 @@ struct pakfire;
 #include <pakfire/key.h>
 #include <pakfire/packagelist.h>
 #include <pakfire/parser.h>
+#include <pakfire/progress.h>
 #include <pakfire/repo.h>
 #include <pakfire/repolist.h>
 #include <pakfire/transaction.h>
@@ -69,6 +70,11 @@ typedef int (*pakfire_pick_solution_callback)
 void pakfire_set_pick_solution_callback(
        struct pakfire* pakfire, pakfire_pick_solution_callback callback, void* data);
 
+typedef int (*pakfire_setup_progress_callback)
+       (struct pakfire* pakfire, struct pakfire_progress* progress, void* data);
+void pakfire_set_setup_progress_callback(
+       struct pakfire* pakfire, pakfire_setup_progress_callback callback, void* data);
+
 const char* pakfire_get_path(struct pakfire* pakfire);
 
 int pakfire_clean(struct pakfire* pakfire, int flags);
@@ -139,6 +145,7 @@ int pakfire_is_mountpoint(struct pakfire* pakfire, const char* path);
 
 int pakfire_confirm(struct pakfire* pakfire, const char* message, const char* question);
 int pakfire_pick_solution(struct pakfire* pakfire, struct pakfire_transaction* transaction);
+int pakfire_setup_progress(struct pakfire* pakfire, struct pakfire_progress* progress);
 
 magic_t pakfire_get_magic(struct pakfire* pakfire);
 
diff --git a/src/libpakfire/include/pakfire/progress.h b/src/libpakfire/include/pakfire/progress.h
new file mode 100644 (file)
index 0000000..5c003b9
--- /dev/null
@@ -0,0 +1,87 @@
+/*#############################################################################
+#                                                                             #
+# Pakfire - The IPFire package management system                              #
+# Copyright (C) 2023 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_PROGRESS_H
+#define PAKFIRE_PROGRESS_H
+
+#include <time.h>
+
+struct pakfire_progress;
+
+enum pakfire_progress_flags {
+       PAKFIRE_PROGRESS_NO_PROGRESS            = (1 << 0),
+       PAKFIRE_PROGRESS_SHOW_PERCENTAGE        = (1 << 1),
+       PAKFIRE_PROGRESS_SHOW_BYTES_TRANSFERRED = (1 << 2),
+       PAKFIRE_PROGRESS_SHOW_COUNTER           = (1 << 3),
+       PAKFIRE_PROGRESS_SHOW_ELAPSED_TIME      = (1 << 4),
+       PAKFIRE_PROGRESS_SHOW_ETA               = (1 << 5),
+       PAKFIRE_PROGRESS_SHOW_TRANSFER_SPEED    = (1 << 6),
+};
+
+int pakfire_progress_has_flag(struct pakfire_progress* p, int flag);
+
+/*
+       Callbacks
+*/
+typedef int (*pakfire_progress_start_callback)
+       (struct pakfire* pakfire, struct pakfire_progress* progress, void* data, unsigned long int value);
+typedef int (*pakfire_progress_finish_callback)
+       (struct pakfire* pakfire, struct pakfire_progress* progress, void* data);
+typedef int (*pakfire_progress_update_callback)
+       (struct pakfire* pakfire, struct pakfire_progress* progress, void* data, unsigned long int value);
+typedef void (*pakfire_progress_free_callback)
+       (struct pakfire* pakfire, struct pakfire_progress* progress, void* data);
+
+void pakfire_progress_set_callback_data(struct pakfire_progress* p, void* data);
+
+void pakfire_progress_set_start_callback(
+       struct pakfire_progress* p, pakfire_progress_start_callback callback);
+void pakfire_progress_set_finish_callback(
+       struct pakfire_progress* p, pakfire_progress_finish_callback callback);
+void pakfire_progress_set_update_callback(
+       struct pakfire_progress* p, pakfire_progress_update_callback callback);
+void pakfire_progress_set_free_callback(
+       struct pakfire_progress* p, pakfire_progress_free_callback callback);
+
+unsigned long int pakfire_progress_get_value(struct pakfire_progress* p);
+unsigned long int pakfire_progress_get_max_value(struct pakfire_progress* p);
+const char* pakfire_progress_get_title(struct pakfire_progress* p);
+double pakfire_progress_get_percentage(struct pakfire_progress* p);
+time_t pakfire_progress_get_elapsed_time(struct pakfire_progress* p);
+time_t pakfire_progress_get_eta(struct pakfire_progress* p);
+double pakfire_progress_get_transfer_speed(struct pakfire_progress* p);
+
+#ifdef PAKFIRE_PRIVATE
+
+#include <pakfire/pakfire.h>
+
+int pakfire_progress_create(struct pakfire_progress** progress,
+       struct pakfire* pakfire, const char* title, int flags);
+
+struct pakfire_progress* pakfire_progress_ref(struct pakfire_progress* p);
+struct pakfire_progress* pakfire_progress_unref(struct pakfire_progress* p);
+
+int pakfire_progress_start(struct pakfire_progress* p, unsigned long int value);
+int pakfire_progress_finish(struct pakfire_progress* p);
+int pakfire_progress_update(struct pakfire_progress* p, unsigned long int value);
+
+#endif
+
+#endif /* PAKFIRE_PROGRESS_H */
index 6fda3812e20bae20e6be3307664e5a09bbb9c2d9..f662d3ec25451a7f11754c5341743cdfc807e547 100644 (file)
@@ -35,6 +35,7 @@ global:
        pakfire_set_confirm_callback;
        pakfire_set_log_callback;
        pakfire_set_pick_solution_callback;
+       pakfire_set_setup_progress_callback;
        pakfire_unref;
        pakfire_version_compare;
        pakfire_whatprovides;
@@ -207,6 +208,21 @@ global:
        pakfire_problem_to_string;
        pakfire_problem_unref;
 
+       # progress
+       pakfire_progress_get_elapsed_time;
+       pakfire_progress_get_eta;
+       pakfire_progress_get_max_value;
+       pakfire_progress_get_percentage;
+       pakfire_progress_get_title;
+       pakfire_progress_get_transfer_speed;
+       pakfire_progress_get_value;
+       pakfire_progress_has_flag;
+       pakfire_progress_set_callback_data;
+       pakfire_progress_set_finish_callback;
+       pakfire_progress_set_free_callback;
+       pakfire_progress_set_start_callback;
+       pakfire_progress_set_update_callback;
+
        # progressbar
        pakfire_progressbar_add_bar;
        pakfire_progressbar_add_bytes_transferred;
index 94f760ccf2da651afd1f85b60734d6291523e754..8cd7bbbb64616c42e762321b58f721ac8f54ba96 100644 (file)
@@ -111,6 +111,10 @@ struct pakfire {
                // Pick Solution
                pakfire_pick_solution_callback pick_solution;
                void* pick_solution_data;
+
+               // Setup Progress
+               pakfire_setup_progress_callback setup_progress;
+               void* setup_progress_data;
        } callbacks;
 
        // Logging
@@ -1097,6 +1101,22 @@ int pakfire_pick_solution(struct pakfire* pakfire, struct pakfire_transaction* t
        return r;
 }
 
+PAKFIRE_EXPORT void pakfire_set_setup_progress_callback(struct pakfire* pakfire,
+               pakfire_setup_progress_callback callback, void* data) {
+       pakfire->callbacks.setup_progress = callback;
+       pakfire->callbacks.setup_progress_data = data;
+}
+
+int pakfire_setup_progress(struct pakfire* pakfire, struct pakfire_progress* progress) {
+       int r = 0;
+
+       if (pakfire->callbacks.setup_progress)
+               r = pakfire->callbacks.setup_progress(pakfire,
+                       progress, pakfire->callbacks.setup_progress_data);
+
+       return r;
+}
+
 PAKFIRE_EXPORT int pakfire_has_flag(struct pakfire* pakfire, const int flag) {
        return pakfire->flags & flag;
 }
diff --git a/src/libpakfire/progress.c b/src/libpakfire/progress.c
new file mode 100644 (file)
index 0000000..5b15f33
--- /dev/null
@@ -0,0 +1,302 @@
+/*#############################################################################
+#                                                                             #
+# Pakfire - The IPFire package management system                              #
+# Copyright (C) 2023 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 <stdlib.h>
+#include <time.h>
+
+#include <pakfire/pakfire.h>
+#include <pakfire/private.h>
+#include <pakfire/progress.h>
+
+struct pakfire_progress {
+       struct pakfire* pakfire;
+       int nrefs;
+
+       // Title
+       const char* title;
+
+       // Flags what to show
+       int flags;
+
+       // Status
+       enum pakfire_progress_status {
+               PAKFIRE_PROGRESS_INIT = 0,
+               PAKFIRE_PROGRESS_RUNNING,
+               PAKFIRE_PROGRESS_FINISHED,
+       } status;
+
+       // Callbacks
+       struct pakfire_progress_callbacks {
+               void* data;
+
+               pakfire_progress_start_callback start;
+               pakfire_progress_finish_callback finish;
+               pakfire_progress_update_callback update;
+               pakfire_progress_free_callback free;
+       } callbacks;
+
+       // Start Time
+       struct timespec time_start;
+       struct timespec time_finished;
+
+       // Values
+       unsigned long int value;
+       unsigned long int max_value;
+};
+
+static void pakfire_progress_free(struct pakfire_progress* p) {
+       // Ensure this is finished
+       pakfire_progress_finish(p);
+
+       // Call the free callback
+       if (p->callbacks.free)
+               p->callbacks.free(p->pakfire, p, p->callbacks.data);
+
+       pakfire_unref(p->pakfire);
+       free(p);
+}
+
+int pakfire_progress_create(struct pakfire_progress** progress,
+               struct pakfire* pakfire, const char* title, int flags) {
+       struct pakfire_progress* p = NULL;
+       int r;
+
+       // Allocate a new object
+       p = calloc(1, sizeof(*p));
+       if (!p)
+               return -errno;
+
+       // Store a reference to Pakfire
+       p->pakfire = pakfire_ref(pakfire);
+
+       // Initialize the reference counter
+       p->nrefs = 1;
+
+       // Store title
+       p->title = title;
+
+       // Store the flags
+       p->flags = flags;
+
+       // Initialize status
+       p->status = PAKFIRE_PROGRESS_INIT;
+
+       // XXX Create a couple of default callbacks
+
+       // Call setup
+       if (!pakfire_progress_has_flag(p, PAKFIRE_PROGRESS_NO_PROGRESS)) {
+               r = pakfire_setup_progress(pakfire, p);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Success
+       *progress = p;
+       return 0;
+
+ERROR:
+       if (p)
+               pakfire_progress_unref(p);
+
+       return r;
+}
+
+struct pakfire_progress* pakfire_progress_ref(struct pakfire_progress* p) {
+       ++p->nrefs;
+
+       return p;
+}
+
+struct pakfire_progress* pakfire_progress_unref(struct pakfire_progress* p) {
+       if (--p->nrefs > 0)
+               return p;
+
+       pakfire_progress_free(p);
+       return NULL;
+}
+
+PAKFIRE_EXPORT int pakfire_progress_has_flag(struct pakfire_progress* p, int flag) {
+       return p->flags & flag;
+}
+
+/*
+       Callback Configuration
+*/
+
+PAKFIRE_EXPORT void pakfire_progress_set_callback_data(struct pakfire_progress* p, void* data) {
+       p->callbacks.data = data;
+}
+
+PAKFIRE_EXPORT void pakfire_progress_set_start_callback(
+               struct pakfire_progress* p, pakfire_progress_start_callback callback) {
+       p->callbacks.start = callback;
+}
+
+PAKFIRE_EXPORT void pakfire_progress_set_finish_callback(
+               struct pakfire_progress* p, pakfire_progress_finish_callback callback) {
+       p->callbacks.finish = callback;
+}
+
+PAKFIRE_EXPORT void pakfire_progress_set_update_callback(
+               struct pakfire_progress* p, pakfire_progress_update_callback callback) {
+       p->callbacks.update = callback;
+}
+
+PAKFIRE_EXPORT void pakfire_progress_set_free_callback(
+               struct pakfire_progress* p, pakfire_progress_free_callback callback) {
+       p->callbacks.free = callback;
+}
+
+int pakfire_progress_start(struct pakfire_progress* p, unsigned long int value) {
+       int r;
+
+       // This can only be called once
+       if (p->status == PAKFIRE_PROGRESS_RUNNING)
+               return -EINVAL;
+
+       // We are now running...
+       p->status = PAKFIRE_PROGRESS_RUNNING;
+
+       // Store the max value
+       p->max_value = value;
+
+       // Set start time
+       r = clock_gettime(CLOCK_REALTIME, &p->time_start);
+       if (r)
+               return r;
+
+       // No-op
+       if (pakfire_progress_has_flag(p, PAKFIRE_PROGRESS_NO_PROGRESS))
+               return 0;
+
+       // Call the start callback
+       if (p->callbacks.start) {
+               r = p->callbacks.start(p->pakfire, p, p->callbacks.data, value);
+               if (r)
+                       return r;
+       }
+
+       // Call the first update
+       return pakfire_progress_update(p, 0);
+}
+
+int pakfire_progress_finish(struct pakfire_progress* p) {
+       int r;
+
+       // Do nothing if already finished
+       if (p->status == PAKFIRE_PROGRESS_FINISHED)
+               return 0;
+
+       // No-op
+       if (pakfire_progress_has_flag(p, PAKFIRE_PROGRESS_NO_PROGRESS))
+               return 0;
+
+       // Set finished time
+       r = clock_gettime(CLOCK_REALTIME, &p->time_finished);
+       if (r)
+               return r;
+
+       // Call the finish callback
+       if (p->callbacks.finish) {
+               r = p->callbacks.finish(p->pakfire, p, p->callbacks.data);
+               if (r)
+                       return r;
+       }
+
+       return 0;
+}
+
+int pakfire_progress_update(struct pakfire_progress* p, unsigned long int value) {
+       int r;
+
+       // Store the new value
+       p->value = value;
+
+       // No-op
+       if (pakfire_progress_has_flag(p, PAKFIRE_PROGRESS_NO_PROGRESS))
+               return 0;
+
+       // Call the update callback
+       if (p->callbacks.update) {
+               r = p->callbacks.update(p->pakfire, p, p->callbacks.data, value);
+               if (r)
+                       return r;
+       }
+
+       return r;
+}
+
+PAKFIRE_EXPORT unsigned long int pakfire_progress_get_value(struct pakfire_progress* p) {
+       return p->value;
+}
+
+PAKFIRE_EXPORT unsigned long int pakfire_progress_get_max_value(struct pakfire_progress* p) {
+       return p->max_value;
+}
+
+PAKFIRE_EXPORT const char* pakfire_progress_get_title(struct pakfire_progress* p) {
+       return p->title;
+}
+
+PAKFIRE_EXPORT double pakfire_progress_get_percentage(struct pakfire_progress* p) {
+       if (!p->max_value)
+               return 0;
+
+       return p->value * 100.0 / p->max_value;
+}
+
+PAKFIRE_EXPORT time_t pakfire_progress_get_elapsed_time(struct pakfire_progress* p) {
+       struct timespec now;
+       int r;
+
+       switch (p->status) {
+               case PAKFIRE_PROGRESS_INIT:
+                       return 0;
+
+               case PAKFIRE_PROGRESS_RUNNING:
+                       r = clock_gettime(CLOCK_REALTIME, &now);
+                       if (r)
+                               return -1;
+
+                       return now.tv_sec - p->time_start.tv_sec;
+
+               case PAKFIRE_PROGRESS_FINISHED:
+                       return p->time_finished.tv_sec - p->time_start.tv_sec;
+       }
+
+       return -1;
+}
+
+PAKFIRE_EXPORT time_t pakfire_progress_get_eta(struct pakfire_progress* p) {
+       const time_t t = pakfire_progress_get_elapsed_time(p);
+       if (t < 0)
+               return t;
+
+       return t * p->max_value / p->value - t;
+}
+
+PAKFIRE_EXPORT double pakfire_progress_get_transfer_speed(struct pakfire_progress* p) {
+       const time_t t = pakfire_progress_get_elapsed_time(p);
+       if (t <= 0)
+               return t;
+
+       return p->value / t;
+}