4 * Copyright (c) 2006-2013 Pacman Development Team <pacman-dev@archlinux.org>
5 * Copyright (c) 2002-2006 by Judd Vinet <jvinet@zeroflux.org>
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.
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.
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/>.
25 #include <sys/types.h> /* off_t */
29 #include <limits.h> /* UINT_MAX */
39 /* download progress bar */
40 static off_t list_xfered
= 0.0;
41 static off_t list_total
= 0.0;
43 /* delayed output during progress bar */
44 static int on_progress
= 0;
45 static alpm_list_t
*output
= NULL
;
47 /* update speed for the fill_progress based functions */
48 #define UPDATE_SPEED_MS 200
51 * Silly little helper function, determines if the caller needs a visual update
52 * since the last time this function was called.
53 * This is made for the two progress bar functions, to prevent flicker.
54 * @param first_call 1 on first call for initialization purposes, 0 otherwise
55 * @return number of milliseconds since last call
57 static long get_update_timediff(int first_call
)
60 static struct timeval last_time
= {0, 0};
62 /* on first call, simply set the last time and return */
64 gettimeofday(&last_time
, NULL
);
66 struct timeval this_time
;
68 suseconds_t diff_usec
;
70 gettimeofday(&this_time
, NULL
);
71 diff_sec
= this_time
.tv_sec
- last_time
.tv_sec
;
72 diff_usec
= this_time
.tv_usec
- last_time
.tv_usec
;
74 retval
= (diff_sec
* 1000) + (diff_usec
/ 1000);
76 /* do not update last_time if interval was too short */
77 if(retval
>= UPDATE_SPEED_MS
) {
78 last_time
= this_time
;
85 /* refactored from cb_trans_progress */
86 static void fill_progress(const int bar_percent
, const int disp_percent
,
89 /* 8 = 1 space + 1 [ + 1 ] + 5 for percent */
90 const int hashlen
= proglen
- 8;
91 const int hash
= bar_percent
* hashlen
/ 100;
92 static int lasthash
= 0, mouth
= 0;
95 if(bar_percent
== 0) {
102 for(i
= hashlen
; i
> 0; --i
) {
103 /* if special progress bar enabled */
105 if(i
> hashlen
- hash
) {
107 } else if(i
== hashlen
- hash
) {
108 if(lasthash
== hash
) {
110 fputs("\033[1;33mC\033[m", stdout
);
112 fputs("\033[1;33mc\033[m", stdout
);
116 mouth
= mouth
== 1 ? 0 : 1;
118 fputs("\033[1;33mC\033[m", stdout
);
120 fputs("\033[1;33mc\033[m", stdout
);
123 } else if(i
% 3 == 0) {
124 fputs("\033[0;37mo\033[m", stdout
);
126 fputs("\033[0;37m \033[m", stdout
);
128 } /* else regular progress bar */
129 else if(i
> hashlen
- hash
) {
137 /* print display percent after progress bar */
138 /* 5 = 1 space + 3 digits + 1 % */
140 printf(" %3d%%", disp_percent
);
143 if(bar_percent
== 100) {
153 /* callback to handle messages/notifications from libalpm transactions */
154 void cb_event(alpm_event_t event
, void *data1
, void *data2
)
160 case ALPM_EVENT_CHECKDEPS_START
:
161 printf(_("checking dependencies...\n"));
163 case ALPM_EVENT_FILECONFLICTS_START
:
164 if(config
->noprogressbar
) {
165 printf(_("checking for file conflicts...\n"));
168 case ALPM_EVENT_RESOLVEDEPS_START
:
169 printf(_("resolving dependencies...\n"));
171 case ALPM_EVENT_INTERCONFLICTS_START
:
172 printf(_("looking for inter-conflicts...\n"));
174 case ALPM_EVENT_ADD_START
:
175 if(config
->noprogressbar
) {
176 printf(_("installing %s...\n"), alpm_pkg_get_name(data1
));
179 case ALPM_EVENT_ADD_DONE
:
180 alpm_logaction(config
->handle
, PACMAN_CALLER_PREFIX
,
181 "installed %s (%s)\n",
182 alpm_pkg_get_name(data1
),
183 alpm_pkg_get_version(data1
));
184 display_optdepends(data1
);
186 case ALPM_EVENT_REMOVE_START
:
187 if(config
->noprogressbar
) {
188 printf(_("removing %s...\n"), alpm_pkg_get_name(data1
));
191 case ALPM_EVENT_REMOVE_DONE
:
192 alpm_logaction(config
->handle
, PACMAN_CALLER_PREFIX
,
194 alpm_pkg_get_name(data1
),
195 alpm_pkg_get_version(data1
));
197 case ALPM_EVENT_UPGRADE_START
:
198 if(config
->noprogressbar
) {
199 printf(_("upgrading %s...\n"), alpm_pkg_get_name(data1
));
202 case ALPM_EVENT_UPGRADE_DONE
:
203 alpm_logaction(config
->handle
, PACMAN_CALLER_PREFIX
,
204 "upgraded %s (%s -> %s)\n",
205 alpm_pkg_get_name(data1
),
206 alpm_pkg_get_version(data2
),
207 alpm_pkg_get_version(data1
));
208 display_new_optdepends(data2
, data1
);
210 case ALPM_EVENT_DOWNGRADE_START
:
211 if(config
->noprogressbar
) {
212 printf(_("downgrading %s...\n"), alpm_pkg_get_name(data1
));
215 case ALPM_EVENT_DOWNGRADE_DONE
:
216 alpm_logaction(config
->handle
, PACMAN_CALLER_PREFIX
,
217 "downgraded %s (%s -> %s)\n",
218 alpm_pkg_get_name(data1
),
219 alpm_pkg_get_version(data2
),
220 alpm_pkg_get_version(data1
));
221 display_new_optdepends(data2
, data1
);
223 case ALPM_EVENT_REINSTALL_START
:
224 if(config
->noprogressbar
) {
225 printf(_("reinstalling %s...\n"), alpm_pkg_get_name(data1
));
228 case ALPM_EVENT_REINSTALL_DONE
:
229 alpm_logaction(config
->handle
, PACMAN_CALLER_PREFIX
,
230 "reinstalled %s (%s)\n",
231 alpm_pkg_get_name(data1
),
232 alpm_pkg_get_version(data1
));
234 case ALPM_EVENT_INTEGRITY_START
:
235 if(config
->noprogressbar
) {
236 printf(_("checking package integrity...\n"));
239 case ALPM_EVENT_KEYRING_START
:
240 if(config
->noprogressbar
) {
241 printf(_("checking keyring...\n"));
244 case ALPM_EVENT_KEY_DOWNLOAD_START
:
245 printf(_("downloading required keys...\n"));
247 case ALPM_EVENT_LOAD_START
:
248 if(config
->noprogressbar
) {
249 printf(_("loading package files...\n"));
252 case ALPM_EVENT_DELTA_INTEGRITY_START
:
253 printf(_("checking delta integrity...\n"));
255 case ALPM_EVENT_DELTA_PATCHES_START
:
256 printf(_("applying deltas...\n"));
258 case ALPM_EVENT_DELTA_PATCH_START
:
259 printf(_("generating %s with %s... "), (char *)data1
, (char *)data2
);
261 case ALPM_EVENT_DELTA_PATCH_DONE
:
262 printf(_("success!\n"));
264 case ALPM_EVENT_DELTA_PATCH_FAILED
:
265 printf(_("failed.\n"));
267 case ALPM_EVENT_SCRIPTLET_INFO
:
268 fputs((const char *)data1
, stdout
);
270 case ALPM_EVENT_RETRIEVE_START
:
271 colon_printf(_("Retrieving packages ...\n"));
273 case ALPM_EVENT_DISKSPACE_START
:
274 if(config
->noprogressbar
) {
275 printf(_("checking available disk space...\n"));
278 case ALPM_EVENT_OPTDEP_REQUIRED
:
279 colon_printf(_("%s optionally requires %s\n"), alpm_pkg_get_name(data1
),
280 alpm_dep_compute_string(data2
));
282 case ALPM_EVENT_DATABASE_MISSING
:
283 if(!config
->op_s_sync
) {
284 pm_printf(ALPM_LOG_WARNING
,
285 "database file for '%s' does not exist\n", (char *)data1
);
288 /* all the simple done events, with fallthrough for each */
289 case ALPM_EVENT_FILECONFLICTS_DONE
:
290 case ALPM_EVENT_CHECKDEPS_DONE
:
291 case ALPM_EVENT_RESOLVEDEPS_DONE
:
292 case ALPM_EVENT_INTERCONFLICTS_DONE
:
293 case ALPM_EVENT_INTEGRITY_DONE
:
294 case ALPM_EVENT_KEYRING_DONE
:
295 case ALPM_EVENT_KEY_DOWNLOAD_DONE
:
296 case ALPM_EVENT_LOAD_DONE
:
297 case ALPM_EVENT_DELTA_INTEGRITY_DONE
:
298 case ALPM_EVENT_DELTA_PATCHES_DONE
:
299 case ALPM_EVENT_DISKSPACE_DONE
:
306 /* callback to handle questions from libalpm transactions (yes/no) */
307 /* TODO this is one of the worst ever functions written. void *data ? wtf */
308 void cb_question(alpm_question_t event
, void *data1
, void *data2
,
309 void *data3
, int *response
)
312 if(event
== ALPM_QUESTION_INSTALL_IGNOREPKG
) {
320 case ALPM_QUESTION_INSTALL_IGNOREPKG
:
321 if(!config
->op_s_downloadonly
) {
322 *response
= yesno(_("%s is in IgnorePkg/IgnoreGroup. Install anyway?"),
323 alpm_pkg_get_name(data1
));
328 case ALPM_QUESTION_REPLACE_PKG
:
329 *response
= yesno(_("Replace %s with %s/%s?"),
330 alpm_pkg_get_name(data1
),
332 alpm_pkg_get_name(data2
));
334 case ALPM_QUESTION_CONFLICT_PKG
:
335 /* data parameters: target package, local package, conflict (strings) */
336 /* print conflict only if it contains new information */
337 if(strcmp(data1
, data3
) == 0 || strcmp(data2
, data3
) == 0) {
338 *response
= noyes(_("%s and %s are in conflict. Remove %s?"),
343 *response
= noyes(_("%s and %s are in conflict (%s). Remove %s?"),
350 case ALPM_QUESTION_REMOVE_PKGS
:
352 alpm_list_t
*unresolved
= data1
;
353 alpm_list_t
*namelist
= NULL
, *i
;
355 for(i
= unresolved
; i
; i
= i
->next
) {
356 namelist
= alpm_list_add(namelist
,
357 (char *)alpm_pkg_get_name(i
->data
));
361 "The following package cannot be upgraded due to unresolvable dependencies:\n",
362 "The following packages cannot be upgraded due to unresolvable dependencies:\n",
364 list_display(" ", namelist
, getcols(fileno(stdout
)));
366 *response
= noyes(_n(
367 "Do you want to skip the above package for this upgrade?",
368 "Do you want to skip the above packages for this upgrade?",
370 alpm_list_free(namelist
);
373 case ALPM_QUESTION_SELECT_PROVIDER
:
375 alpm_list_t
*providers
= data1
;
376 size_t count
= alpm_list_count(providers
);
377 char *depstring
= alpm_dep_compute_string((alpm_depend_t
*)data2
);
378 colon_printf(_("There are %zd providers available for %s:\n"), count
,
381 select_display(providers
);
382 *response
= select_question(count
);
385 case ALPM_QUESTION_CORRUPTED_PKG
:
386 *response
= yesno(_("File %s is corrupted (%s).\n"
387 "Do you want to delete it?"),
389 alpm_strerror(*(alpm_errno_t
*)data2
));
391 case ALPM_QUESTION_IMPORT_KEY
:
393 alpm_pgpkey_t
*key
= data1
;
395 time_t time
= (time_t)key
->created
;
396 strftime(created
, 12, "%Y-%m-%d", localtime(&time
));
399 *response
= yesno(_("Import PGP key %d%c/%s, \"%s\", created: %s (revoked)?"),
400 key
->length
, key
->pubkey_algo
, key
->fingerprint
, key
->uid
, created
);
402 *response
= yesno(_("Import PGP key %d%c/%s, \"%s\", created: %s?"),
403 key
->length
, key
->pubkey_algo
, key
->fingerprint
, key
->uid
, created
);
409 if(config
->ask
& event
) {
410 /* inverse the default answer */
411 *response
= !*response
;
416 /* callback to handle display of transaction progress */
417 void cb_progress(alpm_progress_t event
, const char *pkgname
, int percent
,
418 size_t howmany
, size_t current
)
420 static int prevpercent
;
421 static size_t prevcurrent
;
422 /* size of line to allocate for text printing (e.g. not progressbar) */
427 /* used for wide character width determination and printing */
428 int len
, wclen
, wcwid
, padwid
;
431 const unsigned short cols
= getcols(fileno(stdout
));
433 if(config
->noprogressbar
|| cols
== 0) {
438 get_update_timediff(1);
439 } else if(percent
== 100) {
440 /* no need for timediff update, but unconditionally continue unless we
441 * already completed on a previous call */
442 if(prevpercent
== 100) {
446 if(current
!= prevcurrent
) {
448 } else if(!pkgname
|| percent
== prevpercent
||
449 get_update_timediff(0) < UPDATE_SPEED_MS
) {
450 /* only update the progress bar when we have a package name, the
451 * percentage has changed, and it has been long enough. */
456 prevpercent
= percent
;
457 prevcurrent
= current
;
459 /* set text of message to display */
461 case ALPM_PROGRESS_ADD_START
:
462 opr
= _("installing");
464 case ALPM_PROGRESS_UPGRADE_START
:
465 opr
= _("upgrading");
467 case ALPM_PROGRESS_DOWNGRADE_START
:
468 opr
= _("downgrading");
470 case ALPM_PROGRESS_REINSTALL_START
:
471 opr
= _("reinstalling");
473 case ALPM_PROGRESS_REMOVE_START
:
476 case ALPM_PROGRESS_CONFLICTS_START
:
477 opr
= _("checking for file conflicts");
479 case ALPM_PROGRESS_DISKSPACE_START
:
480 opr
= _("checking available disk space");
482 case ALPM_PROGRESS_INTEGRITY_START
:
483 opr
= _("checking package integrity");
485 case ALPM_PROGRESS_KEYRING_START
:
486 opr
= _("checking keys in keyring");
488 case ALPM_PROGRESS_LOAD_START
:
489 opr
= _("loading package files");
495 infolen
= cols
* 6 / 10;
500 /* find # of digits in package counts to scale output */
506 /* determine room left for non-digits text [not ( 1/12) part] */
507 textlen
= infolen
- 3 /* (/) */ - (2 * digits
) - 1 /* space */;
509 /* In order to deal with characters from all locales, we have to worry
510 * about wide characters and their column widths. A lot of stuff is
511 * done here to figure out the actual number of screen columns used
512 * by the output, and then pad it accordingly so we fill the terminal.
514 /* len = opr len + pkgname len (if available) + space + null */
515 len
= strlen(opr
) + ((pkgname
) ? strlen(pkgname
) : 0) + 2;
516 wcstr
= calloc(len
, sizeof(wchar_t));
517 /* print our strings to the alloc'ed memory */
518 #if defined(HAVE_SWPRINTF)
519 wclen
= swprintf(wcstr
, len
, L
"%s %s", opr
, pkgname
);
521 /* because the format string was simple, we can easily do this without
522 * using swprintf, although it is probably not as safe/fast. The max
523 * chars we can copy is decremented each time by subtracting the length
524 * of the already printed/copied wide char string. */
525 wclen
= mbstowcs(wcstr
, opr
, len
);
526 wclen
+= mbstowcs(wcstr
+ wclen
, " ", len
- wclen
);
527 wclen
+= mbstowcs(wcstr
+ wclen
, pkgname
, len
- wclen
);
529 wcwid
= wcswidth(wcstr
, wclen
);
530 padwid
= textlen
- wcwid
;
531 /* if padwid is < 0, we need to trim the string so padwid = 0 */
535 /* grab the max number of char columns we can fill */
536 while(i
> 0 && wcwidth(*p
) < i
) {
540 /* then add the ellipsis and fill out any extra padding */
546 printf("(%*ld/%*ld) %ls%-*s", digits
, (unsigned long)current
,
547 digits
, (unsigned long)howmany
, wcstr
, padwid
, "");
551 /* call refactored fill progress function */
552 fill_progress(percent
, percent
, cols
- infolen
);
555 alpm_list_t
*i
= NULL
;
557 for(i
= output
; i
; i
= i
->next
) {
558 fputs((const char *)i
->data
, stdout
);
567 /* callback to handle receipt of total download value */
568 void cb_dl_total(off_t total
)
571 /* if we get a 0 value, it means this list has finished downloading,
572 * so clear out our list_xfered as well */
578 /* callback to handle display of download progress */
579 void cb_dl_progress(const char *filename
, off_t file_xfered
, off_t file_total
)
581 static double rate_last
;
582 static off_t xfered_last
;
583 static struct timeval initial_time
;
587 /* used for wide character width determination and printing */
588 int len
, wclen
, wcwid
, padwid
;
591 int totaldownload
= 0;
595 unsigned int eta_h
= 0, eta_m
= 0, eta_s
= 0;
596 double rate_human
, xfered_human
;
597 const char *rate_label
, *xfered_label
;
598 int file_percent
= 0, total_percent
= 0;
600 const unsigned short cols
= getcols(fileno(stdout
));
602 if(config
->noprogressbar
|| cols
== 0 || file_total
== -1) {
603 if(file_xfered
== 0) {
604 printf(_("downloading %s...\n"), filename
);
610 infolen
= cols
* 6 / 10;
614 /* only use TotalDownload if enabled and we have a callback value */
615 if(config
->totaldownload
&& list_total
) {
617 if(list_xfered
+ file_total
<= list_total
) {
620 /* bogus values : don't enable totaldownload and reset */
627 xfered
= list_xfered
+ file_xfered
;
630 xfered
= file_xfered
;
634 /* bogus values : stop here */
639 /* this is basically a switch on xfered: 0, total, and
641 if(file_xfered
== 0) {
642 /* set default starting values, ensure we only call this once
643 * if TotalDownload is enabled */
644 if(!totaldownload
|| (totaldownload
&& list_xfered
== 0)) {
645 gettimeofday(&initial_time
, NULL
);
646 xfered_last
= (off_t
)0;
648 get_update_timediff(1);
650 } else if(file_xfered
== file_total
) {
651 /* compute final values */
652 struct timeval current_time
;
654 suseconds_t diff_usec
;
656 gettimeofday(¤t_time
, NULL
);
657 diff_sec
= current_time
.tv_sec
- initial_time
.tv_sec
;
658 diff_usec
= current_time
.tv_usec
- initial_time
.tv_usec
;
659 timediff
= (diff_sec
* 1000) + (diff_usec
/ 1000);
661 rate
= (double)xfered
/ (timediff
/ 1000.0);
662 /* round elapsed time (in ms) to the nearest second */
663 eta_s
= (unsigned int)(timediff
+ 500) / 1000;
668 /* compute current average values */
669 timediff
= get_update_timediff(0);
671 if(timediff
< UPDATE_SPEED_MS
) {
672 /* return if the calling interval was too short */
675 rate
= (double)(xfered
- xfered_last
) / (timediff
/ 1000.0);
676 /* average rate to reduce jumpiness */
677 rate
= (rate
+ 2 * rate_last
) / 3;
679 eta_s
= (total
- xfered
) / rate
;
684 xfered_last
= xfered
;
688 file_percent
= (file_xfered
* 100) / file_total
;
694 total_percent
= ((list_xfered
+ file_xfered
) * 100) /
697 /* if we are at the end, add the completed file to list_xfered */
698 if(file_xfered
== file_total
) {
699 list_xfered
+= file_total
;
703 /* fix up time for display */
704 eta_h
= eta_s
/ 3600;
705 eta_s
-= eta_h
* 3600;
709 len
= strlen(filename
);
710 fname
= malloc(len
+ 1);
711 memcpy(fname
, filename
, len
);
712 /* strip package or DB extension for cleaner look */
713 if((p
= strstr(fname
, ".pkg")) || (p
= strstr(fname
, ".db"))) {
714 /* tack on a .sig suffix for signatures */
715 if(memcmp(&filename
[len
- 4], ".sig", 4) == 0) {
716 memcpy(p
, ".sig", 4);
718 /* adjust length for later calculations */
726 /* 1 space + filenamelen + 1 space + 6 for size + 1 space + 3 for label +
727 * + 2 spaces + 4 for rate + 1 for label + 2 for /s + 1 space +
728 * 8 for eta, gives us the magic 30 */
729 filenamelen
= infolen
- 30;
730 /* see printf() code, we omit 'HH:' in these conditions */
731 if(eta_h
== 0 || eta_h
>= 100) {
735 /* In order to deal with characters from all locales, we have to worry
736 * about wide characters and their column widths. A lot of stuff is
737 * done here to figure out the actual number of screen columns used
738 * by the output, and then pad it accordingly so we fill the terminal.
740 /* len = filename len + null */
741 wcfname
= calloc(len
+ 1, sizeof(wchar_t));
742 wclen
= mbstowcs(wcfname
, fname
, len
);
743 wcwid
= wcswidth(wcfname
, wclen
);
744 padwid
= filenamelen
- wcwid
;
745 /* if padwid is < 0, we need to trim the string so padwid = 0 */
747 int i
= filenamelen
- 3;
748 wchar_t *wcp
= wcfname
;
749 /* grab the max number of char columns we can fill */
750 while(i
> 0 && wcwidth(*wcp
) < i
) {
754 /* then add the ellipsis and fill out any extra padding */
760 rate_human
= humanize_size((off_t
)rate
, '\0', -1, &rate_label
);
761 xfered_human
= humanize_size(xfered
, '\0', -1, &xfered_label
);
763 printf(" %ls%-*s ", wcfname
, padwid
, "");
764 /* We will show 1.62M/s, 11.6M/s, but 116K/s and 1116K/s */
765 if(rate_human
< 9.995) {
766 printf("%6.1f %3s %4.2f%c/s ",
767 xfered_human
, xfered_label
, rate_human
, rate_label
[0]);
768 } else if(rate_human
< 99.95) {
769 printf("%6.1f %3s %4.1f%c/s ",
770 xfered_human
, xfered_label
, rate_human
, rate_label
[0]);
772 printf("%6.1f %3s %4.f%c/s ",
773 xfered_human
, xfered_label
, rate_human
, rate_label
[0]);
776 printf("%02u:%02u", eta_m
, eta_s
);
777 } else if(eta_h
< 100) {
778 printf("%02u:%02u:%02u", eta_h
, eta_m
, eta_s
);
780 fputs("--:--", stdout
);
787 fill_progress(file_percent
, total_percent
, cols
- infolen
);
789 fill_progress(file_percent
, file_percent
, cols
- infolen
);
794 /* Callback to handle notifications from the library */
795 void cb_log(alpm_loglevel_t level
, const char *fmt
, va_list args
)
797 if(!fmt
|| strlen(fmt
) == 0) {
803 pm_vasprintf(&string
, level
, fmt
, args
);
805 output
= alpm_list_add(output
, string
);
808 pm_vfprintf(stderr
, level
, fmt
, args
);
812 /* vim: set ts=2 sw=2 noet: */