[pacman-dev] [PATCH v3] Implement multibar UI

Anatol Pomozov anatol.pomozov at gmail.com
Fri May 8 19:09:47 UTC 2020


Hello

This commit:
 - moved console_cursor_* to util.c
 - groups UI progress bars information into "struct pacman_multibar_ui"
 - adds dload_payload->signature to track payload type. If the payload
is signature file then no progress events are sent to pacman.c

On Fri, May 8, 2020 at 12:05 PM Anatol Pomozov <anatol.pomozov at gmail.com> wrote:
>
> 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 at gmail.com>
> ---
>  lib/libalpm/be_sync.c |   1 +
>  lib/libalpm/dload.c   |  61 +++----
>  lib/libalpm/dload.h   |   2 +-
>  src/pacman/callback.c | 387 ++++++++++++++++++++++++++++--------------
>  src/pacman/callback.h |   4 +
>  src/pacman/sync.c     |   2 +
>  src/pacman/util.c     |  18 ++
>  src/pacman/util.h     |   4 +
>  8 files changed, 320 insertions(+), 159 deletions(-)
>
> diff --git a/lib/libalpm/be_sync.c b/lib/libalpm/be_sync.c
> index add1a576..86015ab6 100644
> --- a/lib/libalpm/be_sync.c
> +++ b/lib/libalpm/be_sync.c
> @@ -366,6 +366,7 @@ int SYMEXPORT alpm_dbs_update(alpm_handle_t *handle, alpm_list_t *dbs, int force
>                 if(siglevel & ALPM_SIG_DATABASE) {
>                         struct dload_payload *sig_payload;
>                         CALLOC(sig_payload, 1, sizeof(*sig_payload), GOTO_ERR(handle, ALPM_ERR_MEMORY, cleanup));
> +                       sig_payload->signature = 1;
>
>                         /* print filename into a buffer (leave space for separator and .sig) */
>                         len = strlen(db->treename) + strlen(dbext) + 5;
> diff --git a/lib/libalpm/dload.c b/lib/libalpm/dload.c
> index 2f360ab8..9ab0e5c4 100644
> --- a/lib/libalpm/dload.c
> +++ b/lib/libalpm/dload.c
> @@ -90,6 +90,11 @@ static int dload_progress_cb(void *file, curl_off_t dltotal, curl_off_t dlnow,
>         off_t current_size, total_size;
>         alpm_download_event_progress_t cb_data = {0};
>
> +       /* do not print signature files progress bar */
> +       if(payload->signature) {
> +               return 0;
> +       }
> +
>         /* avoid displaying progress bar for redirects with a body */
>         if(payload->respcode >= 300) {
>                 return 0;
> @@ -100,6 +105,11 @@ static int dload_progress_cb(void *file, curl_off_t dltotal, curl_off_t dlnow,
>                 return 1;
>         }
>
> +       if(dlnow < 0 || dltotal <= 0 || dlnow > dltotal) {
> +               /* bogus values : stop here */
> +               return 0;
> +       }
> +
>         current_size = payload->initial_size + dlnow;
>
>         /* is our filesize still under any set limit? */
> @@ -115,34 +125,15 @@ static int dload_progress_cb(void *file, curl_off_t dltotal, curl_off_t dlnow,
>
>         total_size = payload->initial_size + dltotal;
>
> -       if(dltotal == 0 || payload->prevprogress == total_size) {
> +       if(payload->prevprogress == total_size) {
>                 return 0;
>         }
>
> -       /* initialize the progress bar here to avoid displaying it when
> -        * a repo is up to date and nothing gets downloaded.
> -        * payload->handle->dlcb will receive the remote_name
> -        * and the following arguments:
> -        * 0, -1: download initialized
> -        * 0, 0: non-download event
> -        * x {x>0}, x: download complete
> -        * x {x>0, x<y}, y {y > 0}: download progress, expected total is known */
> -       if(!payload->cb_initialized) {
> -               cb_data.downloaded = 0;
> -               cb_data.total = -1;
> -               payload->cb_initialized = 1;
> -       }
> -       if(payload->prevprogress == current_size) {
> -               cb_data.downloaded = 0;
> -               cb_data.total = 0;
> -       } else {
>         /* do NOT include initial_size since it wasn't part of the package's
>          * download_size (nor included in the total download size callback) */
> -               cb_data.downloaded = dlnow;
> -               cb_data.total = dltotal;
> -       }
> +       cb_data.total = dltotal;
> +       cb_data.downloaded = dlnow;
>         payload->handle->dlcb(payload->remote_name, ALPM_DOWNLOAD_PROGRESS, &cb_data);
> -
>         payload->prevprogress = current_size;
>
>         return 0;
> @@ -375,6 +366,8 @@ static int curl_download_internal(struct dload_payload *payload,
>         double remote_size, bytes_dl;
>         struct sigaction orig_sig_pipe, orig_sig_int;
>         CURLcode curlerr;
> +       alpm_download_event_init_t init_cb_data = {0};
> +       alpm_download_event_completed_t completed_cb_data = {0};
>         /* shortcut to our handle within the payload */
>         alpm_handle_t *handle = payload->handle;
>         CURL *curl = curl_easy_init();
> @@ -444,6 +437,8 @@ static int curl_download_internal(struct dload_payload *payload,
>         dload_interrupted = 0;
>         mask_signal(SIGINT, &inthandler, &orig_sig_int);
>
> +       handle->dlcb(payload->remote_name, ALPM_DOWNLOAD_INIT, &init_cb_data);
> +
>         /* perform transfer */
>         curlerr = curl_easy_perform(curl);
>         _alpm_log(handle, ALPM_LOG_DEBUG, "curl returned error %d from transfer\n",
> @@ -604,6 +599,10 @@ cleanup:
>                 raise(SIGINT);
>         }
>
> +       completed_cb_data.total = bytes_dl;
> +       completed_cb_data.result = ret;
> +       handle->dlcb(payload->remote_name, ALPM_DOWNLOAD_COMPLETED, &completed_cb_data);
> +
>         return ret;
>  }
>
> @@ -665,7 +664,6 @@ static int curl_multi_check_finished_download(CURLM *curlm, CURLMsg *msg,
>         long remote_time = -1;
>         struct stat st;
>         char hostname[HOSTNAME_SIZE];
> -       alpm_download_event_completed_t cb_data = {0};
>         int ret = -1;
>
>         curlerr = curl_easy_getinfo(curl, CURLINFO_PRIVATE, &payload);
> @@ -825,9 +823,12 @@ cleanup:
>                 unlink(payload->tempfile_name);
>         }
>
> -       cb_data.total = bytes_dl;
> -       cb_data.result = ret;
> -       handle->dlcb(payload->remote_name, ALPM_DOWNLOAD_COMPLETED, &cb_data);
> +       if(!payload->signature) {
> +               alpm_download_event_completed_t cb_data = {0};
> +               cb_data.total = bytes_dl;
> +               cb_data.result = ret;
> +               handle->dlcb(payload->remote_name, ALPM_DOWNLOAD_COMPLETED, &cb_data);
> +       }
>
>         curl_multi_remove_handle(curlm, curl);
>         curl_easy_cleanup(curl);
> @@ -945,9 +946,11 @@ static int curl_multi_download_internal(alpm_handle_t *handle,
>                         struct dload_payload *payload = payloads->data;
>
>                         if(curl_multi_add_payload(handle, curlm, payload, localpath) == 0) {
> -                               alpm_download_event_init_t cb_data = {.optional = payload->errors_ok};
> +                               if(!payload->signature) {
> +                                       alpm_download_event_init_t cb_data = {.optional = payload->errors_ok};
> +                                       handle->dlcb(payload->remote_name, ALPM_DOWNLOAD_INIT, &cb_data);
> +                               }
>
> -                               handle->dlcb(payload->remote_name, ALPM_DOWNLOAD_INIT, &cb_data);
>                                 payloads = payloads->next;
>                                 // TODO: report that download has started
>                         } else {
> @@ -1129,6 +1132,7 @@ char SYMEXPORT *alpm_fetch_pkgurl(alpm_handle_t *handle, const char *url)
>
>                 sig_filepath = filecache_find_url(handle, payload.fileurl);
>                 if(sig_filepath == NULL) {
> +                       payload.signature = 1;
>                         payload.handle = handle;
>                         payload.trust_remote_name = 1;
>                         payload.force = 1;
> @@ -1184,5 +1188,4 @@ void _alpm_dload_payload_reset_for_retry(struct dload_payload *payload)
>         payload->initial_size += payload->prevprogress;
>         payload->prevprogress = 0;
>         payload->unlink_on_fail = 0;
> -       payload->cb_initialized = 0;
>  }
> diff --git a/lib/libalpm/dload.h b/lib/libalpm/dload.h
> index 3f2fb9ea..8b52cdae 100644
> --- a/lib/libalpm/dload.h
> +++ b/lib/libalpm/dload.h
> @@ -42,7 +42,7 @@ struct dload_payload {
>         int errors_ok;
>         int unlink_on_fail;
>         int trust_remote_name;
> -       int cb_initialized;
> +       int signature; /* specifies if the payload is a signature file */
>  #ifdef HAVE_LIBCURL
>         CURL *curl;
>         char error_buffer[CURL_ERROR_SIZE];
> 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)));
> --
> 2.26.2
>


More information about the pacman-dev mailing list