Send patches - preferably formatted by git format-patch - to patches at archlinux32 dot org.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAnatol Pomozov <anatol.pomozov@gmail.com>2020-04-17 10:36:38 -0700
committerAllan McRae <allan@archlinux.org>2020-05-09 11:58:39 +1000
commitb96e0df4dceaa2677baa1a3563211950708d3e63 (patch)
tree00a5b415673f78eb4ad8bfa2dda037ff3744f35e /src
parentc78eb48d915dc22146073162dda08ddf73c4a508 (diff)
Implement multibar UI
Multiplexed download requires ability to draw UI for multiple active progress bars. To implement it we use ANSI codes to move cursor up/down and then redraw the required progress bar. `pacman_multibar_ui.active_downloads` field represents the list of active downloads that correspond to progress bars. `struct pacman_progress_bar` is a data structure for a progress bar. In some cases (e.g. database downloads) we want to keep progress bars in order. In some other cases (package downloads) we want to move completed items to the top of the screen. Function `multibar_move_completed_up` allows to configure such behavior. Per discussion in the maillist we do not want to show download progress for signature files. Signed-off-by: Anatol Pomozov <anatol.pomozov@gmail.com> Signed-off-by: Allan McRae <allan@archlinux.org>
Diffstat (limited to 'src')
-rw-r--r--src/pacman/callback.c387
-rw-r--r--src/pacman/callback.h4
-rw-r--r--src/pacman/sync.c2
-rw-r--r--src/pacman/util.c18
-rw-r--r--src/pacman/util.h4
5 files changed, 286 insertions, 129 deletions
diff --git a/src/pacman/callback.c b/src/pacman/callback.c
index 613d59d4..c2e516ec 100644
--- a/src/pacman/callback.c
+++ b/src/pacman/callback.c
@@ -18,6 +18,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <assert.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -52,6 +54,50 @@ static alpm_list_t *output = NULL;
#define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC
#endif
+struct pacman_progress_bar {
+ const char *filename;
+ off_t xfered;
+ off_t total_size;
+ uint64_t init_time; /* Time when this download started doing any progress */
+ uint64_t sync_time; /* Last time we updated the bar info */
+ double rate;
+ unsigned int eta; /* ETA in seconds */
+ bool completed; /* transfer is completed */
+};
+
+/* This datastruct represents the state of multiline progressbar UI */
+struct pacman_multibar_ui {
+ /* List of active downloads handled by multibar UI.
+ * Once the first download in the list is completed it is removed
+ * from this list and we never redraw it anymore.
+ * If the download is in this list, then the UI can redraw the progress bar or change
+ * the order of the bars (e.g. moving completed bars to the top of the list)
+ */
+ alpm_list_t *active_downloads; /* List of type 'struct pacman_progress_bar' */
+
+ /* Number of active download bars that multibar UI handles. */
+ size_t active_downloads_num;
+
+ /* Specifies whether a completed progress bar need to be reordered and moved
+ * to the top of the list.
+ */
+ bool move_completed_up;
+
+ /* Cursor position relative to the first active progress bar,
+ * e.g. 0 means the first active progress bar, active_downloads_num-1 means the last bar,
+ * active_downloads_num - is the line below all progress bars.
+ */
+ int cursor_lineno;
+};
+
+struct pacman_multibar_ui multibar_ui = {0};
+
+static void cursor_goto_end(void);
+
+void multibar_move_completed_up(bool value) {
+ multibar_ui.move_completed_up = value;
+}
+
static int64_t get_time_ms(void)
{
#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0) && defined(CLOCK_MONOTONIC_COARSE)
@@ -152,11 +198,7 @@ static void fill_progress(const int bar_percent, const int disp_percent,
printf(" %3d%%", disp_percent);
}
- if(bar_percent == 100) {
- putchar('\n');
- } else {
- putchar('\r');
- }
+ putchar('\r');
fflush(stdout);
}
@@ -346,6 +388,7 @@ void cb_event(alpm_event_t *event)
case ALPM_EVENT_DB_RETRIEVE_FAILED:
case ALPM_EVENT_PKG_RETRIEVE_DONE:
case ALPM_EVENT_PKG_RETRIEVE_FAILED:
+ cursor_goto_end();
flush_output_list();
on_progress = 0;
break;
@@ -629,6 +672,7 @@ void cb_progress(alpm_progress_t event, const char *pkgname, int percent,
fill_progress(percent, percent, cols - infolen);
if(percent == 100) {
+ putchar('\n');
flush_output_list();
on_progress = 0;
} else {
@@ -647,149 +691,87 @@ void cb_dl_total(off_t total)
}
}
-/* callback to handle display of download progress */
-static void dload_progress_event(const char *filename, off_t file_xfered, off_t file_total)
+static int dload_progressbar_enabled(void)
+{
+ return !config->noprogressbar && (getcols() != 0);
+}
+
+/* Goto the line that corresponds to num-th active download */
+static void cursor_goto_bar(int num)
+{
+ if(num > multibar_ui.cursor_lineno) {
+ console_cursor_move_down(num - multibar_ui.cursor_lineno);
+ } else if(num < multibar_ui.cursor_lineno) {
+ console_cursor_move_up(multibar_ui.cursor_lineno - num);
+ }
+ multibar_ui.cursor_lineno = num;
+}
+
+/* Goto the line *after* the last active progress bar */
+static void cursor_goto_end(void)
+{
+ cursor_goto_bar(multibar_ui.active_downloads_num);
+}
+
+/* Returns true if element with the specified name is found, false otherwise */
+static bool find_bar_for_filename(const char *filename, int *index, struct pacman_progress_bar **bar)
+{
+ int i = 0;
+ alpm_list_t *listitem = multibar_ui.active_downloads;
+ for(; listitem; listitem = listitem->next, i++) {
+ struct pacman_progress_bar *b = listitem->data;
+ if (strcmp(b->filename, filename) == 0) {
+ /* we found a progress bar with the given name */
+ *index = i;
+ *bar = b;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void draw_pacman_progress_bar(struct pacman_progress_bar *bar)
{
- static double rate_last;
- static off_t xfered_last;
- static int64_t initial_time = 0;
int infolen;
int filenamelen;
char *fname, *p;
/* used for wide character width determination and printing */
int len, wclen, wcwid, padwid;
wchar_t *wcfname;
-
- int totaldownload = 0;
- off_t xfered, total;
- double rate = 0.0;
- unsigned int eta_h = 0, eta_m = 0, eta_s = 0;
+ unsigned int eta_h = 0, eta_m = 0, eta_s = bar->eta;
double rate_human, xfered_human;
const char *rate_label, *xfered_label;
- int file_percent = 0, total_percent = 0;
+ int file_percent = 0;
const unsigned short cols = getcols();
- /* Nothing has changed since last callback; stop here */
- if(file_xfered == 0 && file_total == 0) {
- return;
- }
-
- if(config->noprogressbar || cols == 0) {
- if(file_xfered == 0 && file_total == -1) {
- printf(_("downloading %s...\n"), filename);
- fflush(stdout);
- }
- return;
- }
-
- infolen = cols * 6 / 10;
- if(infolen < 50) {
- infolen = 50;
- }
- /* only use TotalDownload if enabled and we have a callback value */
- if(config->totaldownload && list_total) {
- /* sanity check */
- if(list_xfered + file_total <= list_total) {
- totaldownload = 1;
- } else {
- /* bogus values : don't enable totaldownload and reset */
- list_xfered = 0;
- list_total = 0;
- }
- }
-
- if(totaldownload) {
- xfered = list_xfered + file_xfered;
- total = list_total;
- } else {
- xfered = file_xfered;
- total = file_total;
- }
-
- /* this is basically a switch on xfered: 0, total, and
- * anything else */
- if(file_xfered == 0 && file_total == -1) {
- /* set default starting values, ensure we only call this once
- * if TotalDownload is enabled */
- if(!totaldownload || (totaldownload && list_xfered == 0)) {
- initial_time = get_time_ms();
- xfered_last = (off_t)0;
- rate_last = 0.0;
- get_update_timediff(1);
- }
- } else if(xfered > total || xfered < 0) {
- /* bogus values : stop here */
- return;
- } else if(file_xfered == file_total) {
- /* compute final values */
- int64_t timediff = get_time_ms() - initial_time;
- if(timediff > 0) {
- rate = (double)xfered / (timediff / 1000.0);
- /* round elapsed time (in ms) to the nearest second */
- eta_s = (unsigned int)(timediff + 500) / 1000;
- } else {
- eta_s = 0;
- }
- } else {
- /* compute current average values */
- int64_t timediff = get_update_timediff(0);
-
- if(timediff < UPDATE_SPEED_MS) {
- /* return if the calling interval was too short */
- return;
- }
- rate = (double)(xfered - xfered_last) / (timediff / 1000.0);
- /* average rate to reduce jumpiness */
- rate = (rate + 2 * rate_last) / 3;
- if(rate > 0.0) {
- eta_s = (total - xfered) / rate;
- } else {
- eta_s = UINT_MAX;
- }
- rate_last = rate;
- xfered_last = xfered;
- }
-
- if(file_total) {
- file_percent = (file_xfered * 100) / file_total;
+ if(bar->total_size) {
+ file_percent = (bar->xfered * 100) / bar->total_size;
} else {
file_percent = 100;
}
- if(totaldownload) {
- total_percent = ((list_xfered + file_xfered) * 100) /
- list_total;
-
- /* if we are at the end, add the completed file to list_xfered */
- if(file_xfered == file_total) {
- list_xfered += file_total;
- }
- }
-
/* fix up time for display */
eta_h = eta_s / 3600;
eta_s -= eta_h * 3600;
eta_m = eta_s / 60;
eta_s -= eta_m * 60;
- len = strlen(filename);
+ len = strlen(bar->filename);
fname = malloc(len + 1);
- memcpy(fname, filename, len + 1);
+ memcpy(fname, bar->filename, len + 1);
/* strip package or DB extension for cleaner look */
if((p = strstr(fname, ".pkg")) || (p = strstr(fname, ".db")) || (p = strstr(fname, ".files"))) {
- /* tack on a .sig suffix for signatures */
- if(memcmp(&filename[len - 4], ".sig", 4) == 0) {
- memcpy(p, ".sig", 4);
-
- /* adjust length for later calculations */
- len = p - fname + 4;
- } else {
- len = p - fname;
- }
+ len = p - fname;
fname[len] = '\0';
}
+ infolen = cols * 6 / 10;
+ if(infolen < 50) {
+ infolen = 50;
+ }
+
/* 1 space + filenamelen + 1 space + 6 for size + 1 space + 3 for label +
* + 2 spaces + 4 for rate + 1 space + 3 for label + 2 for /s + 1 space +
* 8 for eta, gives us the magic 33 */
@@ -824,8 +806,8 @@ static void dload_progress_event(const char *filename, off_t file_xfered, off_t
}
- rate_human = humanize_size((off_t)rate, '\0', -1, &rate_label);
- xfered_human = humanize_size(xfered, '\0', -1, &xfered_label);
+ rate_human = humanize_size((off_t)bar->rate, '\0', -1, &rate_label);
+ xfered_human = humanize_size(bar->xfered, '\0', -1, &xfered_label);
printf(" %ls%-*s ", wcfname, padwid, "");
/* We will show 1.62 MiB/s, 11.6 MiB/s, but 116 KiB/s and 1116 KiB/s */
@@ -850,19 +832,166 @@ static void dload_progress_event(const char *filename, off_t file_xfered, off_t
free(fname);
free(wcfname);
- if(totaldownload) {
- fill_progress(file_percent, total_percent, cols - infolen);
+ fill_progress(file_percent, file_percent, cols - infolen);
+ return;
+}
+
+static void dload_init_event(const char *filename, alpm_download_event_init_t *data)
+{
+ (void)data;
+
+ if(!dload_progressbar_enabled()) {
+ printf(_(" %s downloading...\n"), filename);
+ return;
+ }
+
+ struct pacman_progress_bar *bar = calloc(1, sizeof(struct pacman_progress_bar));
+ assert(bar);
+ bar->filename = filename;
+ bar->init_time = get_time_ms();
+ bar->rate = 0.0;
+ multibar_ui.active_downloads = alpm_list_add(multibar_ui.active_downloads, bar);
+
+ cursor_goto_end();
+ printf(_(" %s downloading...\n"), filename);
+ multibar_ui.cursor_lineno++;
+ multibar_ui.active_downloads_num++;
+}
+
+/* Draws download progress */
+static void dload_progress_event(const char *filename, alpm_download_event_progress_t *data)
+{
+ int index;
+ struct pacman_progress_bar *bar;
+ int64_t curr_time = get_time_ms();
+ double last_chunk_rate;
+ int64_t timediff;
+
+ if(!dload_progressbar_enabled()) {
+ return;
+ }
+
+ assert(find_bar_for_filename(filename, &index, &bar));
+
+ /* compute current average values */
+ timediff = curr_time - bar->sync_time;
+
+ if(timediff < UPDATE_SPEED_MS) {
+ /* return if the calling interval was too short */
+ return;
+ }
+ bar->sync_time = curr_time;
+
+ last_chunk_rate = (double)(data->downloaded - bar->xfered) / (timediff / 1000.0);
+ /* average rate to reduce jumpiness */
+ bar->rate = (last_chunk_rate + 2 * bar->rate) / 3;
+ if(bar->rate > 0.0) {
+ bar->eta = (data->total - data->downloaded) / bar->rate;
} else {
- fill_progress(file_percent, file_percent, cols - infolen);
+ bar->eta = UINT_MAX;
+ }
+
+ /* Total size is received after the download starts. */
+ bar->total_size = data->total;
+ bar->xfered = data->downloaded;
+
+ cursor_goto_bar(index);
+ draw_pacman_progress_bar(bar);
+ fflush(stdout);
+}
+
+/* download completed */
+static void dload_complete_event(const char *filename, alpm_download_event_completed_t *data)
+{
+ int index;
+ struct pacman_progress_bar *bar;
+ int64_t timediff;
+
+ if(!dload_progressbar_enabled()) {
+ return;
+ }
+
+ assert(find_bar_for_filename(filename, &index, &bar));
+ bar->completed = true;
+
+ /* This may not have been initialized if the download finished before
+ * an alpm_download_event_progress_t event happened */
+ bar->total_size = data->total;
+
+ if(data->result == 1) {
+ cursor_goto_bar(index);
+ printf(_(" %s is up to date"), bar->filename);
+ /* The line contains text from previous status. Erase these leftovers. */
+ console_erase_line();
+ } else if(data->result == 0) {
+ /* compute final values */
+ bar->xfered = bar->total_size;
+ timediff = get_time_ms() - bar->init_time;
+
+ /* if transfer was too fast, treat it as a 1ms transfer, for the sake
+ * of the rate calculation */
+ if(timediff < 1)
+ timediff = 1;
+
+ bar->rate = (double)bar->xfered / (timediff / 1000.0);
+ /* round elapsed time (in ms) to the nearest second */
+ bar->eta = (unsigned int)(timediff + 500) / 1000;
+
+ if(multibar_ui.move_completed_up && index != 0) {
+ /* If this item completed then move it to the top.
+ * Swap 0-th bar data with `index`-th one
+ */
+ struct pacman_progress_bar *former_topbar = multibar_ui.active_downloads->data;
+ alpm_list_t *baritem = alpm_list_nth(multibar_ui.active_downloads, index);
+ multibar_ui.active_downloads->data = bar;
+ baritem->data = former_topbar;
+
+ cursor_goto_bar(index);
+ draw_pacman_progress_bar(former_topbar);
+
+ index = 0;
+ }
+
+ cursor_goto_bar(index);
+ draw_pacman_progress_bar(bar);
+ } else {
+ cursor_goto_bar(index);
+ printf(_(" %s failed to download"), bar->filename);
+ console_erase_line();
+ }
+ fflush(stdout);
+
+ /* If the first bar is completed then there is no reason to keep it
+ * in the list as we are not going to redraw it anymore.
+ */
+ while(multibar_ui.active_downloads) {
+ alpm_list_t *head = multibar_ui.active_downloads;
+ struct pacman_progress_bar *j = head->data;
+ if(j->completed) {
+ multibar_ui.cursor_lineno--;
+ multibar_ui.active_downloads_num--;
+ multibar_ui.active_downloads = alpm_list_remove_item(
+ multibar_ui.active_downloads, head);
+ free(head);
+ free(j);
+ } else {
+ break;
+ }
}
- return;
}
+/* Callback to handle display of download progress */
void cb_download(const char *filename, alpm_download_event_type_t event, void *data)
{
- if(event == ALPM_DOWNLOAD_PROGRESS) {
- alpm_download_event_progress_t *progress = data;
- dload_progress_event(filename, progress->downloaded, progress->total);
+ if(event == ALPM_DOWNLOAD_INIT) {
+ dload_init_event(filename, data);
+ } else if(event == ALPM_DOWNLOAD_PROGRESS) {
+ dload_progress_event(filename, data);
+ } else if(event == ALPM_DOWNLOAD_COMPLETED) {
+ dload_complete_event(filename, data);
+ } else {
+ pm_printf(ALPM_LOG_ERROR, _("unknown callback event type %d for %s\n"),
+ event, filename);
}
}
diff --git a/src/pacman/callback.h b/src/pacman/callback.h
index 6d92e86b..09d544a6 100644
--- a/src/pacman/callback.h
+++ b/src/pacman/callback.h
@@ -20,6 +20,7 @@
#ifndef PM_CALLBACK_H
#define PM_CALLBACK_H
+#include <stdbool.h>
#include <sys/types.h> /* off_t */
#include <alpm.h>
@@ -44,4 +45,7 @@ void cb_download(const char *filename, alpm_download_event_type_t event,
__attribute__((format(printf, 2, 0)))
void cb_log(alpm_loglevel_t level, const char *fmt, va_list args);
+/* specify if multibar UI should move completed bars to the top of the screen */
+void multibar_move_completed_up(bool value);
+
#endif /* PM_CALLBACK_H */
diff --git a/src/pacman/sync.c b/src/pacman/sync.c
index f7dcb958..a05af5da 100644
--- a/src/pacman/sync.c
+++ b/src/pacman/sync.c
@@ -35,6 +35,7 @@
#include "pacman.h"
#include "util.h"
#include "package.h"
+#include "callback.h"
#include "conf.h"
static int unlink_verbose(const char *pathname, int ignore_missing)
@@ -824,6 +825,7 @@ int sync_prepare_execute(void)
goto cleanup;
}
+ multibar_move_completed_up(true);
if(alpm_trans_commit(config->handle, &data) == -1) {
alpm_errno_t err = alpm_errno(config->handle);
pm_printf(ALPM_LOG_ERROR, _("failed to commit transaction (%s)\n"),
diff --git a/src/pacman/util.c b/src/pacman/util.c
index 97b8e06d..03035037 100644
--- a/src/pacman/util.c
+++ b/src/pacman/util.c
@@ -1837,3 +1837,21 @@ char *arg_to_string(int argc, char *argv[])
strcpy(p, argv[i]);
return cl_text;
}
+
+/* Moves console cursor `lines` up */
+void console_cursor_move_up(unsigned int lines)
+{
+ printf("\x1B[%dF", lines);
+}
+
+/* Moves console cursor `lines` down */
+void console_cursor_move_down(unsigned int lines)
+{
+ printf("\x1B[%dE", lines);
+}
+
+/* Erases line from the current cursor position till the end of the line */
+void console_erase_line(void)
+{
+ printf("\x1B[K");
+}
diff --git a/src/pacman/util.h b/src/pacman/util.h
index 2b21f3d5..c97048fb 100644
--- a/src/pacman/util.h
+++ b/src/pacman/util.h
@@ -83,6 +83,10 @@ char *arg_to_string(int argc, char *argv[]);
char *safe_fgets_stdin(char *s, int size);
void console_cursor_hide(void);
void console_cursor_show(void);
+void console_cursor_move_up(unsigned int lines);
+void console_cursor_move_down(unsigned int lines);
+/* Erases line from the current cursor position till the end of the line */
+void console_erase_line(void);
int pm_printf(alpm_loglevel_t level, const char *format, ...) __attribute__((format(printf,2,3)));
int pm_asprintf(char **string, const char *format, ...) __attribute__((format(printf,2,3)));