746 lines
20 KiB
C
746 lines
20 KiB
C
/*
|
|
* callback.c
|
|
*
|
|
* Copyright (c) 2006-2011 Pacman Development Team <pacman-dev@archlinux.org>
|
|
* Copyright (c) 2002-2006 by Judd Vinet <jvinet@zeroflux.org>
|
|
*
|
|
* 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 2 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 "config.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/time.h>
|
|
#include <sys/types.h> /* off_t */
|
|
#include <unistd.h>
|
|
#include <wchar.h>
|
|
#include <limits.h> /* UINT_MAX */
|
|
|
|
#include <alpm.h>
|
|
|
|
/* pacman */
|
|
#include "callback.h"
|
|
#include "util.h"
|
|
#include "conf.h"
|
|
|
|
/* download progress bar */
|
|
static off_t list_xfered = 0.0;
|
|
static off_t list_total = 0.0;
|
|
|
|
/* delayed output during progress bar */
|
|
static int on_progress = 0;
|
|
static alpm_list_t *output = NULL;
|
|
|
|
/* Silly little helper function, determines if the caller needs a visual update
|
|
* since the last time this function was called.
|
|
* This is made for the two progress bar functions, to prevent flicker
|
|
*
|
|
* first_call indicates if this is the first time it is called, for
|
|
* initialization purposes */
|
|
static double get_update_timediff(int first_call)
|
|
{
|
|
double retval = 0.0;
|
|
static struct timeval last_time = {0, 0};
|
|
|
|
/* on first call, simply set the last time and return */
|
|
if(first_call) {
|
|
gettimeofday(&last_time, NULL);
|
|
} else {
|
|
struct timeval this_time;
|
|
double diff_sec, diff_usec;
|
|
|
|
gettimeofday(&this_time, NULL);
|
|
diff_sec = this_time.tv_sec - last_time.tv_sec;
|
|
diff_usec = this_time.tv_usec - last_time.tv_usec;
|
|
|
|
retval = diff_sec + (diff_usec / 1000000.0);
|
|
|
|
/* return 0 and do not update last_time if interval was too short */
|
|
if(retval < UPDATE_SPEED_SEC) {
|
|
retval = 0.0;
|
|
} else {
|
|
last_time = this_time;
|
|
}
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
/* refactored from cb_trans_progress */
|
|
static void fill_progress(const int bar_percent, const int disp_percent,
|
|
const int proglen)
|
|
{
|
|
/* 8 = 1 space + 1 [ + 1 ] + 5 for percent */
|
|
const int hashlen = proglen - 8;
|
|
const int hash = bar_percent * hashlen / 100;
|
|
static int lasthash = 0, mouth = 0;
|
|
int i;
|
|
|
|
if(bar_percent == 0) {
|
|
lasthash = 0;
|
|
mouth = 0;
|
|
}
|
|
|
|
if(hashlen > 0) {
|
|
printf(" [");
|
|
for(i = hashlen; i > 0; --i) {
|
|
/* if special progress bar enabled */
|
|
if(config->chomp) {
|
|
if(i > hashlen - hash) {
|
|
printf("-");
|
|
} else if(i == hashlen - hash) {
|
|
if(lasthash == hash) {
|
|
if(mouth) {
|
|
printf("\033[1;33mC\033[m");
|
|
} else {
|
|
printf("\033[1;33mc\033[m");
|
|
}
|
|
} else {
|
|
lasthash = hash;
|
|
mouth = mouth == 1 ? 0 : 1;
|
|
if(mouth) {
|
|
printf("\033[1;33mC\033[m");
|
|
} else {
|
|
printf("\033[1;33mc\033[m");
|
|
}
|
|
}
|
|
} else if(i%3 == 0) {
|
|
printf("\033[0;37mo\033[m");
|
|
} else {
|
|
printf("\033[0;37m \033[m");
|
|
}
|
|
} /* else regular progress bar */
|
|
else if(i > hashlen - hash) {
|
|
printf("#");
|
|
} else {
|
|
printf("-");
|
|
}
|
|
}
|
|
printf("]");
|
|
}
|
|
/* print display percent after progress bar */
|
|
/* 5 = 1 space + 3 digits + 1 % */
|
|
if(proglen >= 5) {
|
|
printf(" %3d%%", disp_percent);
|
|
}
|
|
|
|
if(bar_percent == 100) {
|
|
printf("\n");
|
|
} else {
|
|
printf("\r");
|
|
}
|
|
fflush(stdout);
|
|
}
|
|
|
|
|
|
|
|
/* callback to handle messages/notifications from libalpm transactions */
|
|
void cb_event(alpm_event_t event, void *data1, void *data2)
|
|
{
|
|
if(config->print) {
|
|
return;
|
|
}
|
|
switch(event) {
|
|
case ALPM_EVENT_CHECKDEPS_START:
|
|
printf(_("checking dependencies...\n"));
|
|
break;
|
|
case ALPM_EVENT_FILECONFLICTS_START:
|
|
if(config->noprogressbar) {
|
|
printf(_("checking for file conflicts...\n"));
|
|
}
|
|
break;
|
|
case ALPM_EVENT_RESOLVEDEPS_START:
|
|
printf(_("resolving dependencies...\n"));
|
|
break;
|
|
case ALPM_EVENT_INTERCONFLICTS_START:
|
|
printf(_("looking for inter-conflicts...\n"));
|
|
break;
|
|
case ALPM_EVENT_ADD_START:
|
|
if(config->noprogressbar) {
|
|
printf(_("installing %s...\n"), alpm_pkg_get_name(data1));
|
|
}
|
|
break;
|
|
case ALPM_EVENT_ADD_DONE:
|
|
alpm_logaction(config->handle, "installed %s (%s)\n",
|
|
alpm_pkg_get_name(data1),
|
|
alpm_pkg_get_version(data1));
|
|
display_optdepends(data1);
|
|
break;
|
|
case ALPM_EVENT_REMOVE_START:
|
|
if(config->noprogressbar) {
|
|
printf(_("removing %s...\n"), alpm_pkg_get_name(data1));
|
|
}
|
|
break;
|
|
case ALPM_EVENT_REMOVE_DONE:
|
|
alpm_logaction(config->handle, "removed %s (%s)\n",
|
|
alpm_pkg_get_name(data1),
|
|
alpm_pkg_get_version(data1));
|
|
break;
|
|
case ALPM_EVENT_UPGRADE_START:
|
|
if(config->noprogressbar) {
|
|
printf(_("upgrading %s...\n"), alpm_pkg_get_name(data1));
|
|
}
|
|
break;
|
|
case ALPM_EVENT_UPGRADE_DONE:
|
|
alpm_logaction(config->handle, "upgraded %s (%s -> %s)\n",
|
|
(char *)alpm_pkg_get_name(data1),
|
|
(char *)alpm_pkg_get_version(data2),
|
|
(char *)alpm_pkg_get_version(data1));
|
|
display_new_optdepends(data2,data1);
|
|
break;
|
|
case ALPM_EVENT_INTEGRITY_START:
|
|
if(config->noprogressbar) {
|
|
printf(_("checking package integrity...\n"));
|
|
}
|
|
break;
|
|
case ALPM_EVENT_LOAD_START:
|
|
if(config->noprogressbar) {
|
|
printf(_("loading package files...\n"));
|
|
}
|
|
break;
|
|
case ALPM_EVENT_DELTA_INTEGRITY_START:
|
|
printf(_("checking delta integrity...\n"));
|
|
break;
|
|
case ALPM_EVENT_DELTA_PATCHES_START:
|
|
printf(_("applying deltas...\n"));
|
|
break;
|
|
case ALPM_EVENT_DELTA_PATCH_START:
|
|
printf(_("generating %s with %s... "), (char *)data1, (char *)data2);
|
|
break;
|
|
case ALPM_EVENT_DELTA_PATCH_DONE:
|
|
printf(_("success!\n"));
|
|
break;
|
|
case ALPM_EVENT_DELTA_PATCH_FAILED:
|
|
printf(_("failed.\n"));
|
|
break;
|
|
case ALPM_EVENT_SCRIPTLET_INFO:
|
|
printf("%s", (char *)data1);
|
|
break;
|
|
case ALPM_EVENT_RETRIEVE_START:
|
|
printf(_(":: Retrieving packages from %s...\n"), (char *)data1);
|
|
break;
|
|
case ALPM_EVENT_DISKSPACE_START:
|
|
if(config->noprogressbar) {
|
|
printf(_("checking available disk space...\n"));
|
|
}
|
|
break;
|
|
/* all the simple done events, with fallthrough for each */
|
|
case ALPM_EVENT_FILECONFLICTS_DONE:
|
|
case ALPM_EVENT_CHECKDEPS_DONE:
|
|
case ALPM_EVENT_RESOLVEDEPS_DONE:
|
|
case ALPM_EVENT_INTERCONFLICTS_DONE:
|
|
case ALPM_EVENT_INTEGRITY_DONE:
|
|
case ALPM_EVENT_LOAD_DONE:
|
|
case ALPM_EVENT_DELTA_INTEGRITY_DONE:
|
|
case ALPM_EVENT_DELTA_PATCHES_DONE:
|
|
case ALPM_EVENT_DISKSPACE_DONE:
|
|
/* nothing */
|
|
break;
|
|
}
|
|
fflush(stdout);
|
|
}
|
|
|
|
/* callback to handle questions from libalpm transactions (yes/no) */
|
|
/* TODO this is one of the worst ever functions written. void *data ? wtf */
|
|
void cb_question(alpm_question_t event, void *data1, void *data2,
|
|
void *data3, int *response)
|
|
{
|
|
if(config->print) {
|
|
return;
|
|
}
|
|
switch(event) {
|
|
case ALPM_QUESTION_INSTALL_IGNOREPKG:
|
|
if(!config->op_s_downloadonly) {
|
|
*response = yesno(_(":: %s is in IgnorePkg/IgnoreGroup. Install anyway?"),
|
|
alpm_pkg_get_name(data1));
|
|
} else {
|
|
*response = 1;
|
|
}
|
|
break;
|
|
case ALPM_QUESTION_REPLACE_PKG:
|
|
*response = yesno(_(":: Replace %s with %s/%s?"),
|
|
alpm_pkg_get_name(data1),
|
|
(char *)data3,
|
|
alpm_pkg_get_name(data2));
|
|
break;
|
|
case ALPM_QUESTION_CONFLICT_PKG:
|
|
/* data parameters: target package, local package, conflict (strings) */
|
|
/* print conflict only if it contains new information */
|
|
if(strcmp(data1, data3) == 0 || strcmp(data2, data3) == 0) {
|
|
*response = noyes(_(":: %s and %s are in conflict. Remove %s?"),
|
|
(char *)data1,
|
|
(char *)data2,
|
|
(char *)data2);
|
|
} else {
|
|
*response = noyes(_(":: %s and %s are in conflict (%s). Remove %s?"),
|
|
(char *)data1,
|
|
(char *)data2,
|
|
(char *)data3,
|
|
(char *)data2);
|
|
}
|
|
break;
|
|
case ALPM_QUESTION_REMOVE_PKGS:
|
|
{
|
|
alpm_list_t *unresolved = (alpm_list_t *) data1;
|
|
alpm_list_t *namelist = NULL, *i;
|
|
size_t count = 0;
|
|
for (i = unresolved; i; i = i->next) {
|
|
namelist = alpm_list_add(namelist,
|
|
(char *)alpm_pkg_get_name(i->data));
|
|
count++;
|
|
}
|
|
printf(_n(
|
|
":: The following package cannot be upgraded due to unresolvable dependencies:\n",
|
|
":: The following packages cannot be upgraded due to unresolvable dependencies:\n",
|
|
count));
|
|
list_display(" ", namelist);
|
|
printf("\n");
|
|
*response = noyes(_n(
|
|
"Do you want to skip the above package for this upgrade?",
|
|
"Do you want to skip the above packages for this upgrade?",
|
|
count));
|
|
alpm_list_free(namelist);
|
|
}
|
|
break;
|
|
case ALPM_QUESTION_SELECT_PROVIDER:
|
|
{
|
|
alpm_list_t *providers = (alpm_list_t *)data1;
|
|
size_t count = alpm_list_count(providers);
|
|
char *depstring = alpm_dep_compute_string((alpm_depend_t *)data2);
|
|
printf(_(":: There are %zd providers available for %s:\n"), count,
|
|
depstring);
|
|
free(depstring);
|
|
select_display(providers);
|
|
*response = select_question(count);
|
|
}
|
|
break;
|
|
case ALPM_QUESTION_LOCAL_NEWER:
|
|
if(!config->op_s_downloadonly) {
|
|
*response = yesno(_(":: %s-%s: local version is newer. Upgrade anyway?"),
|
|
alpm_pkg_get_name(data1),
|
|
alpm_pkg_get_version(data1));
|
|
} else {
|
|
*response = 1;
|
|
}
|
|
break;
|
|
case ALPM_QUESTION_CORRUPTED_PKG:
|
|
*response = yesno(_(":: File %s is corrupted (%s).\n"
|
|
"Do you want to delete it?"),
|
|
(char *)data1,
|
|
alpm_strerror(*(enum _alpm_errno_t *)data2));
|
|
break;
|
|
case ALPM_QUESTION_IMPORT_KEY:
|
|
{
|
|
alpm_pgpkey_t *key = data1;
|
|
char created[12];
|
|
strftime(created, 12, "%Y-%m-%d", localtime(&(key->created)));
|
|
*response = yesno(_(":: Import PGP key %s, \"%s\", created %s?"),
|
|
key->fingerprint, key->uid, created);
|
|
}
|
|
break;
|
|
}
|
|
if(config->noask) {
|
|
if(config->ask & event) {
|
|
/* inverse the default answer */
|
|
*response = !*response;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* callback to handle display of transaction progress */
|
|
void cb_progress(alpm_progress_t event, const char *pkgname, int percent,
|
|
size_t howmany, size_t current)
|
|
{
|
|
static int prevpercent;
|
|
static size_t prevcurrent;
|
|
/* size of line to allocate for text printing (e.g. not progressbar) */
|
|
int infolen;
|
|
int digits, textlen;
|
|
size_t tmp;
|
|
char *opr = NULL;
|
|
/* used for wide character width determination and printing */
|
|
int len, wclen, wcwid, padwid;
|
|
wchar_t *wcstr;
|
|
|
|
const unsigned short cols = getcols();
|
|
|
|
if(config->noprogressbar || cols == 0) {
|
|
return;
|
|
}
|
|
|
|
if(percent == 0) {
|
|
get_update_timediff(1);
|
|
} else if(percent == 100) {
|
|
/* no need for timediff update, but unconditionally continue unless we
|
|
* already completed on a previous call */
|
|
if(prevpercent == 100) {
|
|
return;
|
|
}
|
|
} else {
|
|
if(current != prevcurrent) {
|
|
/* update always */
|
|
} else if(!pkgname || percent == prevpercent ||
|
|
get_update_timediff(0) < UPDATE_SPEED_SEC) {
|
|
/* only update the progress bar when we have a package name, the
|
|
* percentage has changed, and it has been long enough. */
|
|
return;
|
|
}
|
|
}
|
|
|
|
prevpercent = percent;
|
|
prevcurrent = current;
|
|
|
|
/* set text of message to display */
|
|
switch (event) {
|
|
case ALPM_PROGRESS_ADD_START:
|
|
opr = _("installing");
|
|
break;
|
|
case ALPM_PROGRESS_UPGRADE_START:
|
|
opr = _("upgrading");
|
|
break;
|
|
case ALPM_PROGRESS_REMOVE_START:
|
|
opr = _("removing");
|
|
break;
|
|
case ALPM_PROGRESS_CONFLICTS_START:
|
|
opr = _("checking for file conflicts");
|
|
break;
|
|
case ALPM_PROGRESS_DISKSPACE_START:
|
|
opr = _("checking available disk space");
|
|
break;
|
|
case ALPM_PROGRESS_INTEGRITY_START:
|
|
opr = _("checking package integrity");
|
|
break;
|
|
case ALPM_PROGRESS_LOAD_START:
|
|
opr = _("loading package files");
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
infolen = cols * 6 / 10;
|
|
if(infolen < 50) {
|
|
infolen = 50;
|
|
}
|
|
|
|
/* find # of digits in package counts to scale output */
|
|
digits = 1;
|
|
tmp = howmany;
|
|
while((tmp /= 10)) {
|
|
++digits;
|
|
}
|
|
/* determine room left for non-digits text [not ( 1/12) part] */
|
|
textlen = infolen - 3 /* (/) */ - (2 * digits) - 1 /* space */;
|
|
|
|
/* In order to deal with characters from all locales, we have to worry
|
|
* about wide characters and their column widths. A lot of stuff is
|
|
* done here to figure out the actual number of screen columns used
|
|
* by the output, and then pad it accordingly so we fill the terminal.
|
|
*/
|
|
/* len = opr len + pkgname len (if available) + space + null */
|
|
len = strlen(opr) + ((pkgname) ? strlen(pkgname) : 0) + 2;
|
|
wcstr = calloc(len, sizeof(wchar_t));
|
|
/* print our strings to the alloc'ed memory */
|
|
#if defined(HAVE_SWPRINTF)
|
|
wclen = swprintf(wcstr, len, L"%s %s", opr, pkgname);
|
|
#else
|
|
/* because the format string was simple, we can easily do this without
|
|
* using swprintf, although it is probably not as safe/fast. The max
|
|
* chars we can copy is decremented each time by subtracting the length
|
|
* of the already printed/copied wide char string. */
|
|
wclen = mbstowcs(wcstr, opr, len);
|
|
wclen += mbstowcs(wcstr + wclen, " ", len - wclen);
|
|
wclen += mbstowcs(wcstr + wclen, pkgname, len - wclen);
|
|
#endif
|
|
wcwid = wcswidth(wcstr, wclen);
|
|
padwid = textlen - wcwid;
|
|
/* if padwid is < 0, we need to trim the string so padwid = 0 */
|
|
if(padwid < 0) {
|
|
int i = textlen - 3;
|
|
wchar_t *p = wcstr;
|
|
/* grab the max number of char columns we can fill */
|
|
while(i > 0 && wcwidth(*p) < i) {
|
|
i -= wcwidth(*p);
|
|
p++;
|
|
}
|
|
/* then add the ellipsis and fill out any extra padding */
|
|
wcscpy(p, L"...");
|
|
padwid = i;
|
|
|
|
}
|
|
|
|
printf("(%*ld/%*ld) %ls%-*s", digits, (unsigned long)current,
|
|
digits, (unsigned long)howmany, wcstr, padwid, "");
|
|
|
|
free(wcstr);
|
|
|
|
/* call refactored fill progress function */
|
|
fill_progress(percent, percent, cols - infolen);
|
|
|
|
if(percent == 100) {
|
|
alpm_list_t *i = NULL;
|
|
on_progress = 0;
|
|
for(i = output; i; i = i->next) {
|
|
printf("%s", (char *)i->data);
|
|
}
|
|
fflush(stdout);
|
|
FREELIST(output);
|
|
} else {
|
|
on_progress = 1;
|
|
}
|
|
}
|
|
|
|
/* callback to handle receipt of total download value */
|
|
void cb_dl_total(off_t total)
|
|
{
|
|
list_total = total;
|
|
/* if we get a 0 value, it means this list has finished downloading,
|
|
* so clear out our list_xfered as well */
|
|
if(total == 0) {
|
|
list_xfered = 0;
|
|
}
|
|
}
|
|
|
|
/* callback to handle display of download progress */
|
|
void cb_dl_progress(const char *filename, off_t file_xfered, off_t file_total)
|
|
{
|
|
static double rate_last;
|
|
static off_t xfered_last;
|
|
static struct timeval initial_time;
|
|
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, timediff = 0.0;
|
|
unsigned int eta_h = 0, eta_m = 0, eta_s = 0;
|
|
double rate_human, xfered_human;
|
|
const char *rate_label, *xfered_label;
|
|
int file_percent = 0, total_percent = 0;
|
|
|
|
const unsigned short cols = getcols();
|
|
|
|
if(config->noprogressbar || cols == 0 || file_total == -1) {
|
|
if(file_xfered == 0) {
|
|
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;
|
|
}
|
|
|
|
/* bogus values : stop here */
|
|
if(xfered > total) {
|
|
return;
|
|
}
|
|
|
|
/* this is basically a switch on xfered: 0, total, and
|
|
* anything else */
|
|
if(file_xfered == 0) {
|
|
/* set default starting values, ensure we only call this once
|
|
* if TotalDownload is enabled */
|
|
if(!totaldownload || (totaldownload && list_xfered == 0)) {
|
|
gettimeofday(&initial_time, NULL);
|
|
xfered_last = (off_t)0;
|
|
rate_last = 0.0;
|
|
get_update_timediff(1);
|
|
}
|
|
} else if(file_xfered == file_total) {
|
|
/* compute final values */
|
|
struct timeval current_time;
|
|
double diff_sec, diff_usec;
|
|
|
|
gettimeofday(¤t_time, NULL);
|
|
diff_sec = current_time.tv_sec - initial_time.tv_sec;
|
|
diff_usec = current_time.tv_usec - initial_time.tv_usec;
|
|
timediff = diff_sec + (diff_usec / 1000000.0);
|
|
if(timediff > 0.0) {
|
|
rate = xfered / timediff;
|
|
/* round elapsed time to the nearest second */
|
|
eta_s = (unsigned int)(timediff + 0.5);
|
|
} else {
|
|
eta_s = 0;
|
|
}
|
|
} else {
|
|
/* compute current average values */
|
|
timediff = get_update_timediff(0);
|
|
|
|
if(timediff < UPDATE_SPEED_SEC) {
|
|
/* return if the calling interval was too short */
|
|
return;
|
|
}
|
|
rate = (xfered - xfered_last) / timediff;
|
|
/* 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;
|
|
} 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);
|
|
fname = malloc(len + 1);
|
|
memcpy(fname, filename, len);
|
|
/* strip package or DB extension for cleaner look */
|
|
if((p = strstr(fname, ".pkg")) || (p = strstr(fname, ".db"))) {
|
|
/* 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;
|
|
}
|
|
}
|
|
fname[len] = '\0';
|
|
|
|
/* 1 space + filenamelen + 1 space + 6 for size + 1 space + 3 for label +
|
|
* + 2 spaces + 4 for rate + 1 for label + 2 for /s + 1 space +
|
|
* 8 for eta, gives us the magic 30 */
|
|
filenamelen = infolen - 30;
|
|
/* see printf() code, we omit 'HH:' in these conditions */
|
|
if(eta_h == 0 || eta_h >= 100) {
|
|
filenamelen += 3;
|
|
}
|
|
|
|
/* In order to deal with characters from all locales, we have to worry
|
|
* about wide characters and their column widths. A lot of stuff is
|
|
* done here to figure out the actual number of screen columns used
|
|
* by the output, and then pad it accordingly so we fill the terminal.
|
|
*/
|
|
/* len = filename len + null */
|
|
wcfname = calloc(len + 1, sizeof(wchar_t));
|
|
wclen = mbstowcs(wcfname, fname, len);
|
|
wcwid = wcswidth(wcfname, wclen);
|
|
padwid = filenamelen - wcwid;
|
|
/* if padwid is < 0, we need to trim the string so padwid = 0 */
|
|
if(padwid < 0) {
|
|
int i = filenamelen - 3;
|
|
wchar_t *wcp = wcfname;
|
|
/* grab the max number of char columns we can fill */
|
|
while(i > 0 && wcwidth(*wcp) < i) {
|
|
i -= wcwidth(*wcp);
|
|
wcp++;
|
|
}
|
|
/* then add the ellipsis and fill out any extra padding */
|
|
wcscpy(wcp, L"...");
|
|
padwid = i;
|
|
|
|
}
|
|
|
|
rate_human = humanize_size((off_t)rate, '\0', &rate_label);
|
|
xfered_human = humanize_size(xfered, '\0', &xfered_label);
|
|
|
|
printf(" %ls%-*s ", wcfname, padwid, "");
|
|
/* We will show 1.6M/s, 11.6M/s, but 116K/s and 1116K/s */
|
|
if(rate_human < 100.0) {
|
|
printf("%6.1f %3s %4.1f%c/s ",
|
|
xfered_human, xfered_label, rate_human, rate_label[0]);
|
|
} else {
|
|
printf("%6.1f %3s %4.f%c/s ",
|
|
xfered_human, xfered_label, rate_human, rate_label[0]);
|
|
}
|
|
if(eta_h == 0) {
|
|
printf("%02u:%02u", eta_m, eta_s);
|
|
} else if(eta_h < 100) {
|
|
printf("%02u:%02u:%02u", eta_h, eta_m, eta_s);
|
|
} else {
|
|
printf("--:--");
|
|
}
|
|
|
|
free(fname);
|
|
free(wcfname);
|
|
|
|
if(totaldownload) {
|
|
fill_progress(file_percent, total_percent, cols - infolen);
|
|
} else {
|
|
fill_progress(file_percent, file_percent, cols - infolen);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Callback to handle notifications from the library */
|
|
void cb_log(alpm_loglevel_t level, const char *fmt, va_list args)
|
|
{
|
|
if(!fmt || strlen(fmt) == 0) {
|
|
return;
|
|
}
|
|
|
|
if(on_progress) {
|
|
char *string = NULL;
|
|
pm_vasprintf(&string, level, fmt, args);
|
|
if(string != NULL) {
|
|
output = alpm_list_add(output, string);
|
|
}
|
|
} else {
|
|
pm_vfprintf(stderr, level, fmt, args);
|
|
}
|
|
}
|
|
|
|
/* vim: set ts=2 sw=2 noet: */
|