+++ /dev/null
-/*#############################################################################
-# #
-# 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 <pthread.h>
-#include <signal.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/ioctl.h>
-#include <sys/queue.h>
-#include <time.h>
-
-#include <pakfire/i18n.h>
-#include <pakfire/private.h>
-#include <pakfire/progressbar.h>
-#include <pakfire/string.h>
-#include <pakfire/util.h>
-
-#define DRAWS_PER_SECOND 20
-
-static const struct timespec TIMER = {
- .tv_sec = 0,
- .tv_nsec = 1000000000 / DRAWS_PER_SECOND,
-};
-
-struct pakfire_progressbar_widget {
- STAILQ_ENTRY(pakfire_progressbar_widget) nodes;
-
- int expandable;
- void* data;
-
- const char* (*print)(struct pakfire_progressbar* p,
- struct pakfire_progressbar_widget* widget, unsigned int width, void* data);
- void (*free)(void* data);
-
- char buffer[1024];
-};
-
-struct pakfire_progressbar {
- int nrefs;
- FILE* f;
-
- volatile enum pakfire_progressbar_status {
- PAKFIRE_PROGRESSBAR_INIT = 0,
- PAKFIRE_PROGRESSBAR_RUNNING,
- PAKFIRE_PROGRESSBAR_FINISHED,
- } status;
-
- // The progress
- volatile unsigned long value;
- volatile unsigned long value_max;
-
- struct timespec time_start;
-
- // Reference to the thread that is drawing the progress bar
- pthread_t renderer;
-
- // Widgets
- STAILQ_HEAD(widgets, pakfire_progressbar_widget) widgets;
- unsigned int num_widgets;
-};
-
-// Store terminal settings globally
-static struct pakfire_terminal {
- int rows;
- int cols;
-} terminal;
-
-static void pakfire_progressbar_update_terminal_size(int signum) {
- const int fd = STDOUT_FILENO;
-
- // Set defaults
- terminal.rows = 80;
- terminal.cols = 20;
-
- // Check if output file is a TTY
- int r = isatty(fd);
- if (r != 1)
- return;
-
- struct winsize w;
- r = ioctl(fd, TIOCGWINSZ, &w);
- if (r)
- return;
-
- // Save result
- terminal.rows = w.ws_row;
- terminal.cols = w.ws_col;
-}
-
-static void pakfire_progressbar_widget_free(struct pakfire_progressbar_widget* widget) {
- // Call own free method
- if (widget->free && widget->data)
- widget->free(widget->data);
-
- free(widget);
-}
-
-static void pakfire_progressbar_free_widgets(struct pakfire_progressbar* p) {
- struct pakfire_progressbar_widget* widget;
-
- // Free widgets
- while (!STAILQ_EMPTY(&p->widgets)) {
- widget = STAILQ_FIRST(&p->widgets);
- STAILQ_REMOVE_HEAD(&p->widgets, nodes);
-
- pakfire_progressbar_widget_free(widget);
- }
-}
-
-static int __pakfire_progressbar_finish(struct pakfire_progressbar* p) {
- int r;
- void* retval = NULL;
-
- // Do nothing if already finished
- if (p->status == PAKFIRE_PROGRESSBAR_FINISHED)
- return 0;
-
- // Set status
- p->status = PAKFIRE_PROGRESSBAR_FINISHED;
-
- // Wait until the render thread is done
- if (p->renderer) {
- r = pthread_join(p->renderer, &retval);
- if (r)
- return r;
- }
-
- return (intptr_t)retval;
-}
-
-static void pakfire_progressbar_free(struct pakfire_progressbar* p) {
- // Ensure the progressbar has finished
- __pakfire_progressbar_finish(p);
-
- pakfire_progressbar_free_widgets(p);
- free(p);
-}
-
-static int pakfire_progressbar_draw(struct pakfire_progressbar* p) {
- struct pakfire_progressbar_widget* widget;
-
- // Update terminal size if not set
- if (!terminal.cols)
- pakfire_progressbar_update_terminal_size(SIGWINCH);
-
- unsigned int cols_left = terminal.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;
-
- // Clear any previous content
- if (*widget->buffer)
- memset(widget->buffer, '\0', sizeof(widget->buffer));
-
- 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;
-}
-
-PAKFIRE_EXPORT int pakfire_progressbar_create(
- struct pakfire_progressbar** progressbar, FILE* f) {
- // Allocate main object
- struct pakfire_progressbar* p = calloc(1, sizeof(*p));
- if (!p)
- return 1;
-
- // Initialise reference counting
- p->nrefs = 1;
-
- // Set output to stdout if nothing was set
- if (!f)
- f = stdout;
-
- // Store output
- p->f = stdout;
-
- // Register signal handler to update terminal size
- signal(SIGWINCH, pakfire_progressbar_update_terminal_size);
-
- // Setup widgets
- STAILQ_INIT(&p->widgets);
-
- // Done
- *progressbar = p;
-
- return 0;
-}
-
-PAKFIRE_EXPORT struct pakfire_progressbar* pakfire_progressbar_ref(struct pakfire_progressbar* p) {
- ++p->nrefs;
-
- return p;
-}
-
-PAKFIRE_EXPORT struct pakfire_progressbar* pakfire_progressbar_unref(struct pakfire_progressbar* p) {
- if (--p->nrefs > 0)
- return p;
-
- pakfire_progressbar_free(p);
- return NULL;
-}
-
-static time_t pakfire_progressbar_elapsed_time(struct pakfire_progressbar* p) {
- struct timespec now;
-
- int r = clock_gettime(CLOCK_REALTIME, &now);
- if (r)
- return r;
-
- return now.tv_sec - p->time_start.tv_sec;
-}
-
-static void* __pakfire_progressbar_renderer(void* __p) {
- struct pakfire_progressbar* p = (struct pakfire_progressbar*)__p;
- int r;
-
- do {
- // Redraw the progress bar
- r = pakfire_progressbar_draw(p);
- if (r)
- goto ERROR;
-
- // Sleep for a short moment
- nanosleep(&TIMER, NULL);
-
- // Keep doing this for as long as we are running
- } while (p->status == PAKFIRE_PROGRESSBAR_RUNNING);
-
- // Set to maximum value
- r = pakfire_progressbar_update(p, p->value_max);
- if (r)
- goto ERROR;
-
- // Perform a final draw
- r = pakfire_progressbar_draw(p);
- if (r)
- goto ERROR;
-
- // Finish line
- r = fputs("\n", p->f);
- if (r <= 0 || r == EOF) {
- r = 1;
- goto ERROR;
- }
-
- // Success
- r = 0;
-
-ERROR:
- return (void *)(intptr_t)r;
-}
-
-PAKFIRE_EXPORT int pakfire_progressbar_start(struct pakfire_progressbar* p, unsigned long value) {
- int r;
-
- if (p->status == PAKFIRE_PROGRESSBAR_RUNNING)
- return EINVAL;
-
- // Set status
- p->status = PAKFIRE_PROGRESSBAR_RUNNING;
-
- // Set maximum value
- pakfire_progressbar_set_max(p, value);
-
- // Set start time
- r = clock_gettime(CLOCK_REALTIME, &p->time_start);
- if (r)
- return r;
-
- // Set initial value
- r = pakfire_progressbar_update(p, 0);
- if (r)
- return r;
-
- // Launch the renderer thread
- return pthread_create(&p->renderer, NULL, __pakfire_progressbar_renderer, p);
-}
-
-PAKFIRE_EXPORT int pakfire_progressbar_update(struct pakfire_progressbar* p, unsigned long value) {
- if (p->status == PAKFIRE_PROGRESSBAR_INIT)
- return EINVAL;
-
- p->value = value;
- return 0;
-}
-
-PAKFIRE_EXPORT int pakfire_progressbar_increment(struct pakfire_progressbar* p, unsigned long value) {
- return pakfire_progressbar_update(p, p->value + value);
-}
-
-PAKFIRE_EXPORT int pakfire_progressbar_finish(struct pakfire_progressbar* p) {
- if (p->status != PAKFIRE_PROGRESSBAR_RUNNING)
- return EINVAL;
-
- return __pakfire_progressbar_finish(p);
-}
-
-PAKFIRE_EXPORT int pakfire_progressbar_reset(struct pakfire_progressbar* p) {
- // Free all widgets
- pakfire_progressbar_free_widgets(p);
-
- // Reset all values
- p->value = 0;
- p->value_max = 0;
- p->time_start.tv_sec = 0;
- p->time_start.tv_nsec = 0;
-
- return 0;
-}
-
-PAKFIRE_EXPORT void pakfire_progressbar_set_max(
- struct pakfire_progressbar* p, unsigned long value) {
- // Store maximum value
- p->value_max = value;
-}
-
-static int pakfire_progressbar_add_widget(struct pakfire_progressbar* p,
- const char* (*print)(struct pakfire_progressbar* p,
- struct pakfire_progressbar_widget* widget, unsigned int width, void* data),
- void (*free)(void* data), int expandable, void* data) {
- // Allocate the widget
- struct pakfire_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;
-}
-
-// String widget
-
-static const char* pakfire_progressbar_string(struct pakfire_progressbar* p,
- struct pakfire_progressbar_widget* widget, unsigned int width, void* data) {
- return (const char*)data;
-}
-
-PAKFIRE_EXPORT int pakfire_progressbar_add_string(struct pakfire_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 pakfire_progressbar_add_widget(p, pakfire_progressbar_string, free, 0, s);
-}
-
-// Counter
-
-static const char* pakfire_progressbar_counter(struct pakfire_progressbar* p,
- struct pakfire_progressbar_widget* widget, unsigned int width, void* data) {
- int r = pakfire_string_format(widget->buffer, "%lu/%lu", p->value, p->value_max);
- if (r)
- return NULL;
-
- return widget->buffer;
-}
-
-PAKFIRE_EXPORT int pakfire_progressbar_add_counter(struct pakfire_progressbar* p) {
- return pakfire_progressbar_add_widget(p, pakfire_progressbar_counter, NULL, 0, NULL);
-}
-
-// Percentage
-
-static const char* pakfire_progressbar_percentage(struct pakfire_progressbar* p,
- struct pakfire_progressbar_widget* widget, unsigned int width, void* data) {
- double percentage = p->value * 100.0 / p->value_max;
-
- int r = pakfire_string_format(widget->buffer, "%3.0f%%", percentage);
- if (r)
- return NULL;
-
- return widget->buffer;
-}
-
-PAKFIRE_EXPORT int pakfire_progressbar_add_percentage(struct pakfire_progressbar* p) {
- return pakfire_progressbar_add_widget(p, pakfire_progressbar_percentage, NULL, 0, NULL);
-}
-
-// Bar
-
-static const char* pakfire_progressbar_bar(struct pakfire_progressbar* p,
- struct pakfire_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->status == PAKFIRE_PROGRESSBAR_FINISHED) {
- for (unsigned int i = 0; i < width; i++)
- widget->buffer[i] = ' ';
- widget->buffer[width] = '\0';
-
- return widget->buffer;
- }
-
- unsigned int fill = 0;
-
- if (p->value_max)
- fill = p->value * (width - 2) / p->value_max;
-
- // 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] = '-';
- }
-
- return widget->buffer;
-}
-
-PAKFIRE_EXPORT int pakfire_progressbar_add_bar(struct pakfire_progressbar* p) {
- return pakfire_progressbar_add_widget(p, pakfire_progressbar_bar, NULL, 1, NULL);
-}
-
-// Timer
-
-static const char* pakfire_progressbar_timer(struct pakfire_progressbar* p,
- struct pakfire_progressbar_widget* widget, unsigned int width, void* data) {
- time_t t = pakfire_progressbar_elapsed_time(p);
- if (t < 0)
- return NULL;
-
- int r = pakfire_string_format(widget->buffer, "%02lu:%02lu", t / 60, t % 60);
- if (r)
- return NULL;
-
- return widget->buffer;
-}
-
-PAKFIRE_EXPORT int pakfire_progressbar_add_timer(struct pakfire_progressbar* p) {
- return pakfire_progressbar_add_widget(p, pakfire_progressbar_timer, NULL, 0, NULL);
-}
-
-// ETA
-
-static const char* pakfire_progressbar_eta(struct pakfire_progressbar* p,
- struct pakfire_progressbar_widget* widget, unsigned int width, void* data) {
- int r;
-
- time_t t = pakfire_progressbar_elapsed_time(p);
- if (t < 0)
- return NULL;
-
- // Show total time when finished
- if (p->status == PAKFIRE_PROGRESSBAR_FINISHED)
- return pakfire_progressbar_timer(p, widget, width, data);
-
- // Print a placeholder when we haven't started yet
- if (p->value == 0) {
- r = pakfire_string_format(widget->buffer, "%-5s: --:--:--", _("ETA"));
- if (r)
- return NULL;
-
- // Show total time when finished
- } else if (p->status == PAKFIRE_PROGRESSBAR_FINISHED) {
- r = pakfire_string_format(widget->buffer, "%-5s: %02lu:%02lu",
- _("Time"), t / 60, t % 60);
- if (r)
- return NULL;
-
- // Calculate the ETA
- } else {
- time_t eta = t * p->value_max / p->value - t;
-
- r = pakfire_string_format(widget->buffer, "%-5s: %02lu:%02lu",
- _("ETA"), eta / 60, eta % 60);
- if (r)
- return NULL;
- }
-
- return widget->buffer;
-}
-
-PAKFIRE_EXPORT int pakfire_progressbar_add_eta(struct pakfire_progressbar* p) {
- return pakfire_progressbar_add_widget(p, pakfire_progressbar_eta, NULL, 0, NULL);
-}
-
-// Bytes Transferred
-
-static const char* pakfire_progressbar_bytes_transferred(struct pakfire_progressbar* p,
- struct pakfire_progressbar_widget* widget, unsigned int width, void* data) {
- char buffer[16];
-
- int r = pakfire_format_size(buffer, p->value);
- if (r < 0)
- return NULL;
-
- // Add padding so that the string is always at least five characters long
- pakfire_string_format(widget->buffer, "%-5s", buffer);
-
- return widget->buffer;
-}
-
-PAKFIRE_EXPORT int pakfire_progressbar_add_bytes_transferred(struct pakfire_progressbar* p) {
- return pakfire_progressbar_add_widget(p, pakfire_progressbar_bytes_transferred, NULL, 0, NULL);
-}
-
-// Speed
-
-static const char* pakfire_progressbar_transfer_speed(struct pakfire_progressbar* p,
- struct pakfire_progressbar_widget* widget, unsigned int width, void* data) {
- if (!p->value)
- return NULL;
-
- time_t t = pakfire_progressbar_elapsed_time(p);
- if (t <= 0)
- return NULL;
-
- int r = pakfire_format_speed(widget->buffer, sizeof(widget->buffer), p->value / t);
- if (r < 0)
- return NULL;
-
- return widget->buffer;
-}
-
-PAKFIRE_EXPORT int pakfire_progressbar_add_transfer_speed(struct pakfire_progressbar* p) {
- return pakfire_progressbar_add_widget(
- p, pakfire_progressbar_transfer_speed, NULL, 0, NULL);
-}