1 /*
2 * callback.c
3 *
4 * Copyright (c) 2006-2018 Pacman Development Team <pacman-dev@archlinux.org>
5 * Copyright (c) 2002-2006 by Judd Vinet <jvinet@zeroflux.org>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <sys/time.h> /* gettimeofday */
25 #include <sys/types.h> /* off_t */
26 #include <stdint.h> /* int64_t */
27 #include <time.h>
28 #include <unistd.h>
29 #include <wchar.h>
30 #include <limits.h> /* UINT_MAX */
31
32 #include <alpm.h>
33
34 /* pacman */
35 #include "pacman.h"
36 #include "callback.h"
37 #include "util.h"
38 #include "conf.h"
39
40 /* download progress bar */
41 static off_t list_xfered = 0.0;
42 static off_t list_total = 0.0;
43
44 /* delayed output during progress bar */
45 static int on_progress = 0;
46 static alpm_list_t *output = NULL;
47
48 /* update speed for the fill_progress based functions */
49 #define UPDATE_SPEED_MS 200
50
51 #if !defined(CLOCK_MONOTONIC_COARSE) && defined(CLOCK_MONOTONIC)
52 #define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC
53 #endif
54
get_time_ms(void)55 static int64_t get_time_ms(void)
56 {
57 #if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0) && defined(CLOCK_MONOTONIC_COARSE)
58 struct timespec ts = {0, 0};
59 clock_gettime(CLOCK_MONOTONIC_COARSE, &ts);
60 return (ts.tv_sec * 1000) + (ts.tv_nsec / 1000000);
61 #else
62 /* darwin doesn't support clock_gettime, fallback to gettimeofday */
63 struct timeval tv = {0, 0};
64 gettimeofday(&tv, NULL);
65 return (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
66 #endif
67 }
68
69 /**
70 * Silly little helper function, determines if the caller needs a visual update
71 * since the last time this function was called.
72 * This is made for the two progress bar functions, to prevent flicker.
73 * @param first_call 1 on first call for initialization purposes, 0 otherwise
74 * @return number of milliseconds since last call
75 */
get_update_timediff(int first_call)76 static int64_t get_update_timediff(int first_call)
77 {
78 int64_t retval = 0;
79 static int64_t last_time = 0;
80
81 /* on first call, simply set the last time and return */
82 if(first_call) {
83 last_time = get_time_ms();
84 } else {
85 int64_t this_time = get_time_ms();
86 retval = this_time - last_time;
87
88 /* do not update last_time if interval was too short */
89 if(retval < 0 || retval >= UPDATE_SPEED_MS) {
90 last_time = this_time;
91 }
92 }
93
94 return retval;
95 }
96
97 /* refactored from cb_trans_progress */
fill_progress(const int bar_percent,const int disp_percent,const int proglen)98 static void fill_progress(const int bar_percent, const int disp_percent,
99 const int proglen)
100 {
101 /* 8 = 1 space + 1 [ + 1 ] + 5 for percent */
102 const int hashlen = proglen > 8 ? proglen - 8 : 0;
103 const int hash = bar_percent * hashlen / 100;
104 static int lasthash = 0, mouth = 0;
105 int i;
106
107 if(bar_percent == 0) {
108 lasthash = 0;
109 mouth = 0;
110 }
111
112 if(hashlen > 0) {
113 fputs(" [", stdout);
114 for(i = hashlen; i > 0; --i) {
115 /* if special progress bar enabled */
116 if(config->chomp) {
117 if(i > hashlen - hash) {
118 putchar('-');
119 } else if(i == hashlen - hash) {
120 if(lasthash == hash) {
121 if(mouth) {
122 fputs("\033[1;33mC\033[m", stdout);
123 } else {
124 fputs("\033[1;33mc\033[m", stdout);
125 }
126 } else {
127 lasthash = hash;
128 mouth = mouth == 1 ? 0 : 1;
129 if(mouth) {
130 fputs("\033[1;33mC\033[m", stdout);
131 } else {
132 fputs("\033[1;33mc\033[m", stdout);
133 }
134 }
135 } else if(i % 3 == 0) {
136 fputs("\033[0;37mo\033[m", stdout);
137 } else {
138 fputs("\033[0;37m \033[m", stdout);
139 }
140 } /* else regular progress bar */
141 else if(i > hashlen - hash) {
142 putchar('#');
143 } else {
144 putchar('-');
145 }
146 }
147 putchar(']');
148 }
149 /* print display percent after progress bar */
150 /* 5 = 1 space + 3 digits + 1 % */
151 if(proglen >= 5) {
152 printf(" %3d%%", disp_percent);
153 }
154
155 if(bar_percent == 100) {
156 putchar('\n');
157 } else {
158 putchar('\r');
159 }
160 fflush(stdout);
161 }
162
number_length(size_t n)163 static int number_length(size_t n)
164 {
165 int digits = 1;
166 while((n /= 10)) {
167 ++digits;
168 }
169
170 return digits;
171 }
172
173 /* callback to handle messages/notifications from libalpm transactions */
cb_event(alpm_event_t * event)174 void cb_event(alpm_event_t *event)
175 {
176 if(config->print) {
177 return;
178 }
179 switch(event->type) {
180 case ALPM_EVENT_HOOK_START:
181 if(event->hook.when == ALPM_HOOK_PRE_TRANSACTION) {
182 colon_printf(_("Running pre-transaction hooks...\n"));
183 } else {
184 colon_printf(_("Running post-transaction hooks...\n"));
185 }
186 break;
187 case ALPM_EVENT_HOOK_RUN_START:
188 {
189 alpm_event_hook_run_t *e = &event->hook_run;
190 int digits = number_length(e->total);
191 printf("(%*zu/%*zu) %s\n", digits, e->position,
192 digits, e->total,
193 e->desc ? e->desc : e->name);
194 }
195 break;
196 case ALPM_EVENT_CHECKDEPS_START:
197 printf(_("checking dependencies...\n"));
198 break;
199 case ALPM_EVENT_FILECONFLICTS_START:
200 if(config->noprogressbar) {
201 printf(_("checking for file conflicts...\n"));
202 }
203 break;
204 case ALPM_EVENT_RESOLVEDEPS_START:
205 printf(_("resolving dependencies...\n"));
206 break;
207 case ALPM_EVENT_INTERCONFLICTS_START:
208 printf(_("looking for conflicting packages...\n"));
209 break;
210 case ALPM_EVENT_TRANSACTION_START:
211 colon_printf(_("Processing package changes...\n"));
212 break;
213 case ALPM_EVENT_PACKAGE_OPERATION_START:
214 if(config->noprogressbar) {
215 alpm_event_package_operation_t *e = &event->package_operation;
216 switch(e->operation) {
217 case ALPM_PACKAGE_INSTALL:
218 printf(_("installing %s...\n"), alpm_pkg_get_name(e->newpkg));
219 break;
220 case ALPM_PACKAGE_UPGRADE:
221 printf(_("upgrading %s...\n"), alpm_pkg_get_name(e->newpkg));
222 break;
223 case ALPM_PACKAGE_REINSTALL:
224 printf(_("reinstalling %s...\n"), alpm_pkg_get_name(e->newpkg));
225 break;
226 case ALPM_PACKAGE_DOWNGRADE:
227 printf(_("downgrading %s...\n"), alpm_pkg_get_name(e->newpkg));
228 break;
229 case ALPM_PACKAGE_REMOVE:
230 printf(_("removing %s...\n"), alpm_pkg_get_name(e->oldpkg));
231 break;
232 }
233 }
234 break;
235 case ALPM_EVENT_PACKAGE_OPERATION_DONE:
236 {
237 alpm_event_package_operation_t *e = &event->package_operation;
238 switch(e->operation) {
239 case ALPM_PACKAGE_INSTALL:
240 display_optdepends(e->newpkg);
241 break;
242 case ALPM_PACKAGE_UPGRADE:
243 case ALPM_PACKAGE_DOWNGRADE:
244 display_new_optdepends(e->oldpkg, e->newpkg);
245 break;
246 case ALPM_PACKAGE_REINSTALL:
247 case ALPM_PACKAGE_REMOVE:
248 break;
249 }
250 }
251 break;
252 case ALPM_EVENT_INTEGRITY_START:
253 if(config->noprogressbar) {
254 printf(_("checking package integrity...\n"));
255 }
256 break;
257 case ALPM_EVENT_KEYRING_START:
258 if(config->noprogressbar) {
259 printf(_("checking keyring...\n"));
260 }
261 break;
262 case ALPM_EVENT_KEY_DOWNLOAD_START:
263 printf(_("downloading required keys...\n"));
264 break;
265 case ALPM_EVENT_LOAD_START:
266 if(config->noprogressbar) {
267 printf(_("loading package files...\n"));
268 }
269 break;
270 case ALPM_EVENT_DELTA_INTEGRITY_START:
271 printf(_("checking delta integrity...\n"));
272 break;
273 case ALPM_EVENT_DELTA_PATCHES_START:
274 printf(_("applying deltas...\n"));
275 break;
276 case ALPM_EVENT_DELTA_PATCH_START:
277 printf(_("generating %s with %s... "),
278 event->delta_patch.delta->to,
279 event->delta_patch.delta->delta);
280 break;
281 case ALPM_EVENT_DELTA_PATCH_DONE:
282 printf(_("success!\n"));
283 break;
284 case ALPM_EVENT_DELTA_PATCH_FAILED:
285 printf(_("failed.\n"));
286 break;
287 case ALPM_EVENT_SCRIPTLET_INFO:
288 fputs(event->scriptlet_info.line, stdout);
289 break;
290 case ALPM_EVENT_RETRIEVE_START:
291 colon_printf(_("Retrieving packages...\n"));
292 break;
293 case ALPM_EVENT_DISKSPACE_START:
294 if(config->noprogressbar) {
295 printf(_("checking available disk space...\n"));
296 }
297 break;
298 case ALPM_EVENT_OPTDEP_REMOVAL:
299 {
300 alpm_event_optdep_removal_t *e = &event->optdep_removal;
301 char *dep_string = alpm_dep_compute_string(e->optdep);
302 colon_printf(_("%s optionally requires %s\n"),
303 alpm_pkg_get_name(e->pkg),
304 dep_string);
305 free(dep_string);
306 }
307 break;
308 case ALPM_EVENT_DATABASE_MISSING:
309 if(!config->op_s_sync) {
310 pm_printf(ALPM_LOG_WARNING,
311 "database file for '%s' does not exist (use '%s' to download)\n",
312 event->database_missing.dbname,
313 config->op == PM_OP_FILES ? "-Fy": "-Sy");
314 }
315 break;
316 case ALPM_EVENT_PACNEW_CREATED:
317 {
318 alpm_event_pacnew_created_t *e = &event->pacnew_created;
319 if(on_progress) {
320 char *string = NULL;
321 pm_sprintf(&string, ALPM_LOG_WARNING, _("%s installed as %s.pacnew\n"),
322 e->file, e->file);
323 if(string != NULL) {
324 output = alpm_list_add(output, string);
325 }
326 } else {
327 pm_printf(ALPM_LOG_WARNING, _("%s installed as %s.pacnew\n"),
328 e->file, e->file);
329 }
330 }
331 break;
332 case ALPM_EVENT_PACSAVE_CREATED:
333 {
334 alpm_event_pacsave_created_t *e = &event->pacsave_created;
335 if(on_progress) {
336 char *string = NULL;
337 pm_sprintf(&string, ALPM_LOG_WARNING, _("%s saved as %s.pacsave\n"),
338 e->file, e->file);
339 if(string != NULL) {
340 output = alpm_list_add(output, string);
341 }
342 } else {
343 pm_printf(ALPM_LOG_WARNING, _("%s saved as %s.pacsave\n"),
344 e->file, e->file);
345 }
346 }
347 break;
348 /* all the simple done events, with fallthrough for each */
349 case ALPM_EVENT_FILECONFLICTS_DONE:
350 case ALPM_EVENT_CHECKDEPS_DONE:
351 case ALPM_EVENT_RESOLVEDEPS_DONE:
352 case ALPM_EVENT_INTERCONFLICTS_DONE:
353 case ALPM_EVENT_TRANSACTION_DONE:
354 case ALPM_EVENT_INTEGRITY_DONE:
355 case ALPM_EVENT_KEYRING_DONE:
356 case ALPM_EVENT_KEY_DOWNLOAD_DONE:
357 case ALPM_EVENT_LOAD_DONE:
358 case ALPM_EVENT_DELTA_INTEGRITY_DONE:
359 case ALPM_EVENT_DELTA_PATCHES_DONE:
360 case ALPM_EVENT_DISKSPACE_DONE:
361 case ALPM_EVENT_RETRIEVE_DONE:
362 case ALPM_EVENT_RETRIEVE_FAILED:
363 case ALPM_EVENT_HOOK_DONE:
364 case ALPM_EVENT_HOOK_RUN_DONE:
365 /* we can safely ignore those as well */
366 case ALPM_EVENT_PKGDOWNLOAD_START:
367 case ALPM_EVENT_PKGDOWNLOAD_DONE:
368 case ALPM_EVENT_PKGDOWNLOAD_FAILED:
369 /* nothing */
370 break;
371 }
372 fflush(stdout);
373 }
374
375 /* callback to handle questions from libalpm transactions (yes/no) */
cb_question(alpm_question_t * question)376 void cb_question(alpm_question_t *question)
377 {
378 if(config->print) {
379 switch(question->type) {
380 case ALPM_QUESTION_INSTALL_IGNOREPKG:
381 case ALPM_QUESTION_REPLACE_PKG:
382 question->any.answer = 1;
383 break;
384 default:
385 question->any.answer = 0;
386 break;
387 }
388 return;
389 }
390 switch(question->type) {
391 case ALPM_QUESTION_INSTALL_IGNOREPKG:
392 {
393 alpm_question_install_ignorepkg_t *q = &question->install_ignorepkg;
394 if(!config->op_s_downloadonly) {
395 q->install = yesno(_("%s is in IgnorePkg/IgnoreGroup. Install anyway?"),
396 alpm_pkg_get_name(q->pkg));
397 } else {
398 q->install = 1;
399 }
400 }
401 break;
402 case ALPM_QUESTION_REPLACE_PKG:
403 {
404 alpm_question_replace_t *q = &question->replace;
405 q->replace = yesno(_("Replace %s with %s/%s?"),
406 alpm_pkg_get_name(q->oldpkg),
407 alpm_db_get_name(q->newdb),
408 alpm_pkg_get_name(q->newpkg));
409 }
410 break;
411 case ALPM_QUESTION_CONFLICT_PKG:
412 {
413 alpm_question_conflict_t *q = &question->conflict;
414 /* print conflict only if it contains new information */
415 if(strcmp(q->conflict->package1, q->conflict->reason->name) == 0
416 || strcmp(q->conflict->package2, q->conflict->reason->name) == 0) {
417 q->remove = noyes(_("%s and %s are in conflict. Remove %s?"),
418 q->conflict->package1,
419 q->conflict->package2,
420 q->conflict->package2);
421 } else {
422 q->remove = noyes(_("%s and %s are in conflict (%s). Remove %s?"),
423 q->conflict->package1,
424 q->conflict->package2,
425 q->conflict->reason->name,
426 q->conflict->package2);
427 }
428 }
429 break;
430 case ALPM_QUESTION_REMOVE_PKGS:
431 {
432 alpm_question_remove_pkgs_t *q = &question->remove_pkgs;
433 alpm_list_t *namelist = NULL, *i;
434 size_t count = 0;
435 for(i = q->packages; i; i = i->next) {
436 namelist = alpm_list_add(namelist,
437 (char *)alpm_pkg_get_name(i->data));
438 count++;
439 }
440 colon_printf(_n(
441 "The following package cannot be upgraded due to unresolvable dependencies:\n",
442 "The following packages cannot be upgraded due to unresolvable dependencies:\n",
443 count));
444 list_display(" ", namelist, getcols());
445 printf("\n");
446 q->skip = noyes(_n(
447 "Do you want to skip the above package for this upgrade?",
448 "Do you want to skip the above packages for this upgrade?",
449 count));
450 alpm_list_free(namelist);
451 }
452 break;
453 case ALPM_QUESTION_SELECT_PROVIDER:
454 {
455 alpm_question_select_provider_t *q = &question->select_provider;
456 size_t count = alpm_list_count(q->providers);
457 char *depstring = alpm_dep_compute_string(q->depend);
458 colon_printf(_n("There is %zu provider available for %s\n",
459 "There are %zu providers available for %s:\n", count),
460 count, depstring);
461 free(depstring);
462 select_display(q->providers);
463 q->use_index = select_question(count);
464 }
465 break;
466 case ALPM_QUESTION_CORRUPTED_PKG:
467 {
468 alpm_question_corrupted_t *q = &question->corrupted;
469 q->remove = yesno(_("File %s is corrupted (%s).\n"
470 "Do you want to delete it?"),
471 q->filepath,
472 alpm_strerror(q->reason));
473 }
474 break;
475 case ALPM_QUESTION_IMPORT_KEY:
476 {
477 alpm_question_import_key_t *q = &question->import_key;
478 char created[12];
479 time_t time = (time_t)q->key->created;
480 strftime(created, 12, "%Y-%m-%d", localtime(&time));
481
482 if(q->key->revoked) {
483 q->import = yesno(_("Import PGP key %u%c/%s, \"%s\", created: %s (revoked)?"),
484 q->key->length, q->key->pubkey_algo, q->key->fingerprint, q->key->uid, created);
485 } else {
486 q->import = yesno(_("Import PGP key %u%c/%s, \"%s\", created: %s?"),
487 q->key->length, q->key->pubkey_algo, q->key->fingerprint, q->key->uid, created);
488 }
489 }
490 break;
491 }
492 if(config->noask) {
493 if(config->ask & question->type) {
494 /* inverse the default answer */
495 question->any.answer = !question->any.answer;
496 }
497 }
498 }
499
500 /* callback to handle display of transaction progress */
cb_progress(alpm_progress_t event,const char * pkgname,int percent,size_t howmany,size_t current)501 void cb_progress(alpm_progress_t event, const char *pkgname, int percent,
502 size_t howmany, size_t current)
503 {
504 static int prevpercent;
505 static size_t prevcurrent;
506 /* size of line to allocate for text printing (e.g. not progressbar) */
507 int infolen;
508 int digits, textlen;
509 char *opr = NULL;
510 /* used for wide character width determination and printing */
511 int len, wclen, wcwid, padwid;
512 wchar_t *wcstr;
513
514 const unsigned short cols = getcols();
515
516 if(config->noprogressbar || cols == 0) {
517 return;
518 }
519
520 if(percent == 0) {
521 get_update_timediff(1);
522 } else if(percent == 100) {
523 /* no need for timediff update, but unconditionally continue unless we
524 * already completed on a previous call */
525 if(prevpercent == 100) {
526 return;
527 }
528 } else {
529 if(current != prevcurrent) {
530 /* update always */
531 } else if(!pkgname || percent == prevpercent ||
532 get_update_timediff(0) < UPDATE_SPEED_MS) {
533 /* only update the progress bar when we have a package name, the
534 * percentage has changed, and it has been long enough. */
535 return;
536 }
537 }
538
539 prevpercent = percent;
540 prevcurrent = current;
541
542 /* set text of message to display */
543 switch(event) {
544 case ALPM_PROGRESS_ADD_START:
545 opr = _("installing");
546 break;
547 case ALPM_PROGRESS_UPGRADE_START:
548 opr = _("upgrading");
549 break;
550 case ALPM_PROGRESS_DOWNGRADE_START:
551 opr = _("downgrading");
552 break;
553 case ALPM_PROGRESS_REINSTALL_START:
554 opr = _("reinstalling");
555 break;
556 case ALPM_PROGRESS_REMOVE_START:
557 opr = _("removing");
558 break;
559 case ALPM_PROGRESS_CONFLICTS_START:
560 opr = _("checking for file conflicts");
561 break;
562 case ALPM_PROGRESS_DISKSPACE_START:
563 opr = _("checking available disk space");
564 break;
565 case ALPM_PROGRESS_INTEGRITY_START:
566 opr = _("checking package integrity");
567 break;
568 case ALPM_PROGRESS_KEYRING_START:
569 opr = _("checking keys in keyring");
570 break;
571 case ALPM_PROGRESS_LOAD_START:
572 opr = _("loading package files");
573 break;
574 default:
575 return;
576 }
577
578 infolen = cols * 6 / 10;
579 if(infolen < 50) {
580 infolen = 50;
581 }
582
583 /* find # of digits in package counts to scale output */
584 digits = number_length(howmany);
585
586 /* determine room left for non-digits text [not ( 1/12) part] */
587 textlen = infolen - 3 /* (/) */ - (2 * digits) - 1 /* space */;
588
589 /* In order to deal with characters from all locales, we have to worry
590 * about wide characters and their column widths. A lot of stuff is
591 * done here to figure out the actual number of screen columns used
592 * by the output, and then pad it accordingly so we fill the terminal.
593 */
594 /* len = opr len + pkgname len (if available) + space + null */
595 len = strlen(opr) + ((pkgname) ? strlen(pkgname) : 0) + 2;
596 wcstr = calloc(len, sizeof(wchar_t));
597 /* print our strings to the alloc'ed memory */
598 #if defined(HAVE_SWPRINTF)
599 wclen = swprintf(wcstr, len, L"%s %s", opr, pkgname);
600 #else
601 /* because the format string was simple, we can easily do this without
602 * using swprintf, although it is probably not as safe/fast. The max
603 * chars we can copy is decremented each time by subtracting the length
604 * of the already printed/copied wide char string. */
605 wclen = mbstowcs(wcstr, opr, len);
606 wclen += mbstowcs(wcstr + wclen, " ", len - wclen);
607 wclen += mbstowcs(wcstr + wclen, pkgname, len - wclen);
608 #endif
609 wcwid = wcswidth(wcstr, wclen);
610 padwid = textlen - wcwid;
611 /* if padwid is < 0, we need to trim the string so padwid = 0 */
612 if(padwid < 0) {
613 int i = textlen - 3;
614 wchar_t *p = wcstr;
615 /* grab the max number of char columns we can fill */
616 while(i - wcwidth(*p) > 0) {
617 i -= wcwidth(*p);
618 p++;
619 }
620 /* then add the ellipsis and fill out any extra padding */
621 wcscpy(p, L"...");
622 padwid = i;
623
624 }
625
626 printf("(%*zu/%*zu) %ls%-*s", digits, current,
627 digits, howmany, wcstr, padwid, "");
628
629 free(wcstr);
630
631 /* call refactored fill progress function */
632 fill_progress(percent, percent, cols - infolen);
633
634 if(percent == 100) {
635 alpm_list_t *i = NULL;
636 on_progress = 0;
637 fflush(stdout);
638 for(i = output; i; i = i->next) {
639 fputs((const char *)i->data, stderr);
640 }
641 fflush(stderr);
642 FREELIST(output);
643 } else {
644 on_progress = 1;
645 }
646 }
647
648 /* callback to handle receipt of total download value */
cb_dl_total(off_t total)649 void cb_dl_total(off_t total)
650 {
651 list_total = total;
652 /* if we get a 0 value, it means this list has finished downloading,
653 * so clear out our list_xfered as well */
654 if(total == 0) {
655 list_xfered = 0;
656 }
657 }
658
659 /* callback to handle display of download progress */
cb_dl_progress(const char * filename,off_t file_xfered,off_t file_total)660 void cb_dl_progress(const char *filename, off_t file_xfered, off_t file_total)
661 {
662 static double rate_last;
663 static off_t xfered_last;
664 static int64_t initial_time = 0;
665 int infolen;
666 int filenamelen;
667 char *fname, *p;
668 /* used for wide character width determination and printing */
669 int len, wclen, wcwid, padwid;
670 wchar_t *wcfname;
671
672 int totaldownload = 0;
673 off_t xfered, total;
674 double rate = 0.0;
675 unsigned int eta_h = 0, eta_m = 0, eta_s = 0;
676 double rate_human, xfered_human;
677 const char *rate_label, *xfered_label;
678 int file_percent = 0, total_percent = 0;
679
680 const unsigned short cols = getcols();
681
682 /* Nothing has changed since last callback; stop here */
683 if(file_xfered == 0 && file_total == 0) {
684 return;
685 }
686
687 if(config->noprogressbar || cols == 0) {
688 if(file_xfered == 0 && file_total == -1) {
689 printf(_("downloading %s...\n"), filename);
690 fflush(stdout);
691 }
692 return;
693 }
694
695 infolen = cols * 6 / 10;
696 if(infolen < 50) {
697 infolen = 50;
698 }
699 /* only use TotalDownload if enabled and we have a callback value */
700 if(config->totaldownload && list_total) {
701 /* sanity check */
702 if(list_xfered + file_total <= list_total) {
703 totaldownload = 1;
704 } else {
705 /* bogus values : don't enable totaldownload and reset */
706 list_xfered = 0;
707 list_total = 0;
708 }
709 }
710
711 if(totaldownload) {
712 xfered = list_xfered + file_xfered;
713 total = list_total;
714 } else {
715 xfered = file_xfered;
716 total = file_total;
717 }
718
719 /* this is basically a switch on xfered: 0, total, and
720 * anything else */
721 if(file_xfered == 0 && file_total == -1) {
722 /* set default starting values, ensure we only call this once
723 * if TotalDownload is enabled */
724 if(!totaldownload || (totaldownload && list_xfered == 0)) {
725 initial_time = get_time_ms();
726 xfered_last = (off_t)0;
727 rate_last = 0.0;
728 get_update_timediff(1);
729 }
730 } else if(xfered > total || xfered < 0) {
731 /* bogus values : stop here */
732 return;
733 } else if(file_xfered == file_total) {
734 /* compute final values */
735 int64_t timediff = get_time_ms() - initial_time;
736 if(timediff > 0) {
737 rate = (double)xfered / (timediff / 1000.0);
738 /* round elapsed time (in ms) to the nearest second */
739 eta_s = (unsigned int)(timediff + 500) / 1000;
740 } else {
741 eta_s = 0;
742 }
743 } else {
744 /* compute current average values */
745 int64_t timediff = get_update_timediff(0);
746
747 if(timediff < UPDATE_SPEED_MS) {
748 /* return if the calling interval was too short */
749 return;
750 }
751 rate = (double)(xfered - xfered_last) / (timediff / 1000.0);
752 /* average rate to reduce jumpiness */
753 rate = (rate + 2 * rate_last) / 3;
754 if(rate > 0.0) {
755 eta_s = (total - xfered) / rate;
756 } else {
757 eta_s = UINT_MAX;
758 }
759 rate_last = rate;
760 xfered_last = xfered;
761 }
762
763 if(file_total) {
764 file_percent = (file_xfered * 100) / file_total;
765 } else {
766 file_percent = 100;
767 }
768
769 if(totaldownload) {
770 total_percent = ((list_xfered + file_xfered) * 100) /
771 list_total;
772
773 /* if we are at the end, add the completed file to list_xfered */
774 if(file_xfered == file_total) {
775 list_xfered += file_total;
776 }
777 }
778
779 /* fix up time for display */
780 eta_h = eta_s / 3600;
781 eta_s -= eta_h * 3600;
782 eta_m = eta_s / 60;
783 eta_s -= eta_m * 60;
784
785 len = strlen(filename);
786 fname = malloc(len + 1);
787 memcpy(fname, filename, len);
788 /* strip package or DB extension for cleaner look */
789 if((p = strstr(fname, ".pkg")) || (p = strstr(fname, ".db")) || (p = strstr(fname, ".files"))) {
790 /* tack on a .sig suffix for signatures */
791 if(memcmp(&filename[len - 4], ".sig", 4) == 0) {
792 memcpy(p, ".sig", 4);
793
794 /* adjust length for later calculations */
795 len = p - fname + 4;
796 } else {
797 len = p - fname;
798 }
799 }
800 fname[len] = '\0';
801
802 /* 1 space + filenamelen + 1 space + 6 for size + 1 space + 3 for label +
803 * + 2 spaces + 4 for rate + 1 for label + 2 for /s + 1 space +
804 * 8 for eta, gives us the magic 30 */
805 filenamelen = infolen - 30;
806 /* see printf() code, we omit 'HH:' in these conditions */
807 if(eta_h == 0 || eta_h >= 100) {
808 filenamelen += 3;
809 }
810
811 /* In order to deal with characters from all locales, we have to worry
812 * about wide characters and their column widths. A lot of stuff is
813 * done here to figure out the actual number of screen columns used
814 * by the output, and then pad it accordingly so we fill the terminal.
815 */
816 /* len = filename len + null */
817 wcfname = calloc(len + 1, sizeof(wchar_t));
818 wclen = mbstowcs(wcfname, fname, len);
819 wcwid = wcswidth(wcfname, wclen);
820 padwid = filenamelen - wcwid;
821 /* if padwid is < 0, we need to trim the string so padwid = 0 */
822 if(padwid < 0) {
823 int i = filenamelen - 3;
824 wchar_t *wcp = wcfname;
825 /* grab the max number of char columns we can fill */
826 while(i > 0 && wcwidth(*wcp) < i) {
827 i -= wcwidth(*wcp);
828 wcp++;
829 }
830 /* then add the ellipsis and fill out any extra padding */
831 wcscpy(wcp, L"...");
832 padwid = i;
833
834 }
835
836 rate_human = humanize_size((off_t)rate, '\0', -1, &rate_label);
837 xfered_human = humanize_size(xfered, '\0', -1, &xfered_label);
838
839 printf(" %ls%-*s ", wcfname, padwid, "");
840 /* We will show 1.62M/s, 11.6M/s, but 116K/s and 1116K/s */
841 if(rate_human < 9.995) {
842 printf("%6.1f %3s %4.2f%c/s ",
843 xfered_human, xfered_label, rate_human, rate_label[0]);
844 } else if(rate_human < 99.95) {
845 printf("%6.1f %3s %4.1f%c/s ",
846 xfered_human, xfered_label, rate_human, rate_label[0]);
847 } else {
848 printf("%6.1f %3s %4.f%c/s ",
849 xfered_human, xfered_label, rate_human, rate_label[0]);
850 }
851 if(eta_h == 0) {
852 printf("%02u:%02u", eta_m, eta_s);
853 } else if(eta_h < 100) {
854 printf("%02u:%02u:%02u", eta_h, eta_m, eta_s);
855 } else {
856 fputs("--:--", stdout);
857 }
858
859 free(fname);
860 free(wcfname);
861
862 if(totaldownload) {
863 fill_progress(file_percent, total_percent, cols - infolen);
864 } else {
865 fill_progress(file_percent, file_percent, cols - infolen);
866 }
867 return;
868 }
869
870 /* Callback to handle notifications from the library */
cb_log(alpm_loglevel_t level,const char * fmt,va_list args)871 void cb_log(alpm_loglevel_t level, const char *fmt, va_list args)
872 {
873 if(!fmt || strlen(fmt) == 0) {
874 return;
875 }
876
877 if(on_progress) {
878 char *string = NULL;
879 pm_vasprintf(&string, level, fmt, args);
880 if(string != NULL) {
881 output = alpm_list_add(output, string);
882 }
883 } else {
884 pm_vfprintf(stderr, level, fmt, args);
885 }
886 }
887