4 * Copyright (c) 2006-2011 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/>.
27 #include <sys/types.h> /* off_t */
38 /* download progress bar */
39 static double rate_last
;
40 static off_t xfered_last
;
41 static off_t list_xfered
= 0.0;
42 static off_t list_total
= 0.0;
43 static struct timeval initial_time
;
45 /* transaction progress bar */
46 static int prevpercent
= 0; /* for less progressbar output */
48 /* delayed output during progress bar */
49 static int on_progress
= 0;
50 static alpm_list_t
*output
= NULL
;
52 /* Silly little helper function, determines if the caller needs a visual update
53 * since the last time this function was called.
54 * This is made for the two progress bar functions, to prevent flicker
56 * first_call indicates if this is the first time it is called, for
57 * initialization purposes */
58 static double get_update_timediff(int first_call
)
61 static struct timeval last_time
= {0, 0};
63 /* on first call, simply set the last time and return */
65 gettimeofday(&last_time
, NULL
);
67 struct timeval this_time
;
68 double diff_sec
, 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
+ (diff_usec
/ 1000000.0);
76 /* return 0 and do not update last_time if interval was too short */
77 if(retval
< UPDATE_SPEED_SEC
) {
80 last_time
= this_time
;
87 /* refactored from cb_trans_progress */
88 static void fill_progress(const int bar_percent
, const int disp_percent
,
91 /* 8 = 1 space + 1 [ + 1 ] + 5 for percent */
92 const int hashlen
= proglen
- 8;
93 const int hash
= bar_percent
* hashlen
/ 100;
94 static int lasthash
= 0, mouth
= 0;
97 if(bar_percent
== 0) {
104 for(i
= hashlen
; i
> 0; --i
) {
105 /* if special progress bar enabled */
107 if(i
> hashlen
- hash
) {
109 } else if(i
== hashlen
- hash
) {
110 if(lasthash
== hash
) {
112 printf("\033[1;33mC\033[m");
114 printf("\033[1;33mc\033[m");
118 mouth
= mouth
== 1 ? 0 : 1;
120 printf("\033[1;33mC\033[m");
122 printf("\033[1;33mc\033[m");
125 } else if(i
%3 == 0) {
126 printf("\033[0;37mo\033[m");
128 printf("\033[0;37m \033[m");
130 } /* else regular progress bar */
131 else if(i
> hashlen
- hash
) {
139 /* print display percent after progress bar */
140 /* 5 = 1 space + 3 digits + 1 % */
142 printf(" %3d%%", disp_percent
);
145 if(bar_percent
== 100) {
155 /* callback to handle messages/notifications from libalpm transactions */
156 void cb_trans_evt(pmtransevt_t event
, void *data1
, void *data2
)
159 case PM_TRANS_EVT_CHECKDEPS_START
:
160 printf(_("checking dependencies...\n"));
162 case PM_TRANS_EVT_FILECONFLICTS_START
:
163 if(config
->noprogressbar
) {
164 printf(_("checking for file conflicts...\n"));
167 case PM_TRANS_EVT_RESOLVEDEPS_START
:
168 printf(_("resolving dependencies...\n"));
170 case PM_TRANS_EVT_INTERCONFLICTS_START
:
171 printf(_("looking for inter-conflicts...\n"));
173 case PM_TRANS_EVT_ADD_START
:
174 if(config
->noprogressbar
) {
175 printf(_("installing %s...\n"), alpm_pkg_get_name(data1
));
178 case PM_TRANS_EVT_ADD_DONE
:
179 alpm_logaction("installed %s (%s)\n",
180 alpm_pkg_get_name(data1
),
181 alpm_pkg_get_version(data1
));
182 display_optdepends(data1
);
184 case PM_TRANS_EVT_REMOVE_START
:
185 if(config
->noprogressbar
) {
186 printf(_("removing %s...\n"), alpm_pkg_get_name(data1
));
189 case PM_TRANS_EVT_REMOVE_DONE
:
190 alpm_logaction("removed %s (%s)\n",
191 alpm_pkg_get_name(data1
),
192 alpm_pkg_get_version(data1
));
194 case PM_TRANS_EVT_UPGRADE_START
:
195 if(config
->noprogressbar
) {
196 printf(_("upgrading %s...\n"), alpm_pkg_get_name(data1
));
199 case PM_TRANS_EVT_UPGRADE_DONE
:
200 alpm_logaction("upgraded %s (%s -> %s)\n",
201 (char *)alpm_pkg_get_name(data1
),
202 (char *)alpm_pkg_get_version(data2
),
203 (char *)alpm_pkg_get_version(data1
));
204 display_new_optdepends(data2
,data1
);
206 case PM_TRANS_EVT_INTEGRITY_START
:
207 if(config
->noprogressbar
) {
208 printf(_("checking package integrity...\n"));
211 case PM_TRANS_EVT_DELTA_INTEGRITY_START
:
212 printf(_("checking delta integrity...\n"));
214 case PM_TRANS_EVT_DELTA_PATCHES_START
:
215 printf(_("applying deltas...\n"));
217 case PM_TRANS_EVT_DELTA_PATCH_START
:
218 printf(_("generating %s with %s... "), (char *)data1
, (char *)data2
);
220 case PM_TRANS_EVT_DELTA_PATCH_DONE
:
221 printf(_("success!\n"));
223 case PM_TRANS_EVT_DELTA_PATCH_FAILED
:
224 printf(_("failed.\n"));
226 case PM_TRANS_EVT_SCRIPTLET_INFO
:
227 printf("%s", (char*)data1
);
229 case PM_TRANS_EVT_RETRIEVE_START
:
230 printf(_(":: Retrieving packages from %s...\n"), (char*)data1
);
232 case PM_TRANS_EVT_DISKSPACE_START
:
233 if(config
->noprogressbar
) {
234 printf(_("checking available disk space...\n"));
237 /* all the simple done events, with fallthrough for each */
238 case PM_TRANS_EVT_FILECONFLICTS_DONE
:
239 case PM_TRANS_EVT_CHECKDEPS_DONE
:
240 case PM_TRANS_EVT_RESOLVEDEPS_DONE
:
241 case PM_TRANS_EVT_INTERCONFLICTS_DONE
:
242 case PM_TRANS_EVT_INTEGRITY_DONE
:
243 case PM_TRANS_EVT_DELTA_INTEGRITY_DONE
:
244 case PM_TRANS_EVT_DELTA_PATCHES_DONE
:
245 case PM_TRANS_EVT_DISKSPACE_DONE
:
252 /* callback to handle questions from libalpm transactions (yes/no) */
253 /* TODO this is one of the worst ever functions written. void *data ? wtf */
254 void cb_trans_conv(pmtransconv_t event
, void *data1
, void *data2
,
255 void *data3
, int *response
)
258 case PM_TRANS_CONV_INSTALL_IGNOREPKG
:
259 *response
= yesno(_(":: %s is in IgnorePkg/IgnoreGroup. Install anyway?"),
260 alpm_pkg_get_name(data1
));
262 case PM_TRANS_CONV_REPLACE_PKG
:
263 *response
= yesno(_(":: Replace %s with %s/%s?"),
264 alpm_pkg_get_name(data1
),
266 alpm_pkg_get_name(data2
));
268 case PM_TRANS_CONV_CONFLICT_PKG
:
269 /* data parameters: target package, local package, conflict (strings) */
270 /* print conflict only if it contains new information */
271 if(strcmp(data1
, data3
) == 0 || strcmp(data2
, data3
) == 0) {
272 *response
= noyes(_(":: %s and %s are in conflict. Remove %s?"),
277 *response
= noyes(_(":: %s and %s are in conflict (%s). Remove %s?"),
284 case PM_TRANS_CONV_REMOVE_PKGS
:
286 alpm_list_t
*unresolved
= (alpm_list_t
*) data1
;
287 alpm_list_t
*namelist
= NULL
, *i
;
288 for (i
= unresolved
; i
; i
= i
->next
) {
289 namelist
= alpm_list_add(namelist
,
290 (char *)alpm_pkg_get_name(i
->data
));
293 ":: The following package cannot be upgraded due to unresolvable dependencies:\n",
294 ":: The following packages cannot be upgraded due to unresolvable dependencies:\n",
295 alpm_list_count(namelist
)));
296 list_display(" ", namelist
);
298 *response
= noyes(_n(
299 "Do you want to skip the above package for this upgrade?",
300 "Do you want to skip the above packages for this upgrade?",
301 alpm_list_count(namelist
)));
302 alpm_list_free(namelist
);
305 case PM_TRANS_CONV_SELECT_PROVIDER
:
307 alpm_list_t
*providers
= (alpm_list_t
*)data1
;
308 int count
= alpm_list_count(providers
);
309 char *depstring
= alpm_dep_compute_string((pmdepend_t
*)data2
);
310 printf(_(":: There are %d providers available for %s:\n"), count
,
313 select_display(providers
);
315 *response
= select_question(count
);
318 case PM_TRANS_CONV_LOCAL_NEWER
:
319 if(!config
->op_s_downloadonly
) {
320 *response
= yesno(_(":: %s-%s: local version is newer. Upgrade anyway?"),
321 alpm_pkg_get_name(data1
),
322 alpm_pkg_get_version(data1
));
327 case PM_TRANS_CONV_CORRUPTED_PKG
:
328 *response
= yesno(_(":: File %s is corrupted. Do you want to delete it?"),
333 if(config
->ask
& event
) {
334 /* inverse the default answer */
335 *response
= !*response
;
340 /* callback to handle display of transaction progress */
341 void cb_trans_progress(pmtransprog_t event
, const char *pkgname
, int percent
,
342 size_t howmany
, size_t current
)
344 /* size of line to allocate for text printing (e.g. not progressbar) */
349 /* used for wide character width determination and printing */
350 int len
, wclen
, wcwid
, padwid
;
353 if(config
->noprogressbar
) {
358 get_update_timediff(1);
359 } else if(percent
== 100) {
360 /* no need for timediff update, but unconditionally continue unless we
361 * already completed on a previous call */
362 if(prevpercent
== 100) {
366 if(!pkgname
|| percent
== prevpercent
|| get_update_timediff(0) < UPDATE_SPEED_SEC
) {
367 /* only update the progress bar when we have a package name, the
368 * percentage has changed, and it has been long enough. */
373 prevpercent
= percent
;
375 /* set text of message to display */
377 case PM_TRANS_PROGRESS_ADD_START
:
378 opr
= _("installing");
380 case PM_TRANS_PROGRESS_UPGRADE_START
:
381 opr
= _("upgrading");
383 case PM_TRANS_PROGRESS_REMOVE_START
:
386 case PM_TRANS_PROGRESS_CONFLICTS_START
:
387 opr
= _("checking for file conflicts");
389 case PM_TRANS_PROGRESS_DISKSPACE_START
:
390 opr
= _("checking available disk space");
392 case PM_TRANS_PROGRESS_INTEGRITY_START
:
393 opr
= _("checking package integrity");
399 infolen
= getcols() * 6 / 10;
404 /* find # of digits in package counts to scale output */
410 /* determine room left for non-digits text [not ( 1/12) part] */
411 textlen
= infolen
- 3 /* (/) */ - (2 * digits
) - 1 /* space */;
413 /* In order to deal with characters from all locales, we have to worry
414 * about wide characters and their column widths. A lot of stuff is
415 * done here to figure out the actual number of screen columns used
416 * by the output, and then pad it accordingly so we fill the terminal.
418 /* len = opr len + pkgname len (if available) + space + null */
419 len
= strlen(opr
) + ((pkgname
) ? strlen(pkgname
) : 0) + 2;
420 wcstr
= calloc(len
, sizeof(wchar_t));
421 /* print our strings to the alloc'ed memory */
422 #if defined(HAVE_SWPRINTF)
423 wclen
= swprintf(wcstr
, len
, L
"%s %s", opr
, pkgname
);
425 /* because the format string was simple, we can easily do this without
426 * using swprintf, although it is probably not as safe/fast. The max
427 * chars we can copy is decremented each time by subtracting the length
428 * of the already printed/copied wide char string. */
429 wclen
= mbstowcs(wcstr
, opr
, len
);
430 wclen
+= mbstowcs(wcstr
+ wclen
, " ", len
- wclen
);
431 wclen
+= mbstowcs(wcstr
+ wclen
, pkgname
, len
- wclen
);
433 wcwid
= wcswidth(wcstr
, wclen
);
434 padwid
= textlen
- wcwid
;
435 /* if padwid is < 0, we need to trim the string so padwid = 0 */
439 /* grab the max number of char columns we can fill */
440 while(i
> 0 && wcwidth(*p
) < i
) {
444 /* then add the ellipsis and fill out any extra padding */
450 printf("(%*ld/%*ld) %ls%-*s", digits
, (unsigned long)current
,
451 digits
, (unsigned long)howmany
, wcstr
, padwid
, "");
455 /* call refactored fill progress function */
456 fill_progress(percent
, percent
, getcols() - infolen
);
459 alpm_list_t
*i
= NULL
;
461 for(i
= output
; i
; i
= i
->next
) {
462 printf("%s", (char *)i
->data
);
471 /* callback to handle receipt of total download value */
472 void cb_dl_total(off_t total
)
475 /* if we get a 0 value, it means this list has finished downloading,
476 * so clear out our list_xfered as well */
482 /* callback to handle display of download progress */
483 void cb_dl_progress(const char *filename
, off_t file_xfered
, off_t file_total
)
488 /* used for wide character width determination and printing */
489 int len
, wclen
, wcwid
, padwid
;
492 int totaldownload
= 0;
494 double rate
= 0.0, timediff
= 0.0, f_xfered
= 0.0;
495 unsigned int eta_h
= 0, eta_m
= 0, eta_s
= 0;
496 int file_percent
= 0, total_percent
= 0;
497 char rate_size
= 'K', xfered_size
= 'K';
499 if(config
->noprogressbar
|| file_total
== -1) {
500 if(file_xfered
== 0) {
501 printf(_("downloading %s...\n"), filename
);
507 infolen
= getcols() * 6 / 10;
511 /* explanation of magic 28 number at the end */
512 filenamelen
= infolen
- 28;
514 /* only use TotalDownload if enabled and we have a callback value */
515 if(config
->totaldownload
&& list_total
) {
517 if(list_xfered
+ file_total
<= list_total
) {
520 /* bogus values : don't enable totaldownload and reset */
527 xfered
= list_xfered
+ file_xfered
;
530 xfered
= file_xfered
;
534 /* bogus values : stop here */
539 /* this is basically a switch on xfered: 0, total, and
541 if(file_xfered
== 0) {
542 /* set default starting values, ensure we only call this once
543 * if TotalDownload is enabled */
544 if(!totaldownload
|| (totaldownload
&& list_xfered
== 0)) {
545 gettimeofday(&initial_time
, NULL
);
546 xfered_last
= (off_t
)0;
548 get_update_timediff(1);
550 } else if(file_xfered
== file_total
) {
551 /* compute final values */
552 struct timeval current_time
;
553 double diff_sec
, diff_usec
;
555 gettimeofday(¤t_time
, NULL
);
556 diff_sec
= current_time
.tv_sec
- initial_time
.tv_sec
;
557 diff_usec
= current_time
.tv_usec
- initial_time
.tv_usec
;
558 timediff
= diff_sec
+ (diff_usec
/ 1000000.0);
559 rate
= xfered
/ (timediff
* 1024.0);
561 /* round elapsed time to the nearest second */
562 eta_s
= (int)(timediff
+ 0.5);
564 /* compute current average values */
565 timediff
= get_update_timediff(0);
567 if(timediff
< UPDATE_SPEED_SEC
) {
568 /* return if the calling interval was too short */
571 rate
= (xfered
- xfered_last
) / (timediff
* 1024.0);
572 /* average rate to reduce jumpiness */
573 rate
= (rate
+ 2 * rate_last
) / 3;
574 eta_s
= (total
- xfered
) / (rate
* 1024.0);
576 xfered_last
= xfered
;
579 file_percent
= (file_xfered
* 100) / file_total
;
582 total_percent
= ((list_xfered
+ file_xfered
) * 100) /
585 /* if we are at the end, add the completed file to list_xfered */
586 if(file_xfered
== file_total
) {
587 list_xfered
+= file_total
;
591 /* fix up time for display */
592 eta_h
= eta_s
/ 3600;
593 eta_s
-= eta_h
* 3600;
597 fname
= strdup(filename
);
598 /* strip package or DB extension for cleaner look */
599 if((p
= strstr(fname
, ".pkg")) || (p
= strstr(fname
, ".db"))) {
602 /* In order to deal with characters from all locales, we have to worry
603 * about wide characters and their column widths. A lot of stuff is
604 * done here to figure out the actual number of screen columns used
605 * by the output, and then pad it accordingly so we fill the terminal.
607 /* len = filename len + null */
608 len
= strlen(filename
) + 1;
609 wcfname
= calloc(len
, sizeof(wchar_t));
610 wclen
= mbstowcs(wcfname
, fname
, len
);
611 wcwid
= wcswidth(wcfname
, wclen
);
612 padwid
= filenamelen
- wcwid
;
613 /* if padwid is < 0, we need to trim the string so padwid = 0 */
615 int i
= filenamelen
- 3;
616 wchar_t *p
= wcfname
;
617 /* grab the max number of char columns we can fill */
618 while(i
> 0 && wcwidth(*p
) < i
) {
622 /* then add the ellipsis and fill out any extra padding */
628 /* Awesome formatting for progress bar. We need a mess of Kb->Mb->Gb stuff
629 * here. We'll use limit of 2048 for each until we get some empirical */
630 /* rate_size = 'K'; was set above */
637 /* we should not go higher than this for a few years (9999.9 Gb/s?)*/
641 f_xfered
= xfered
/ 1024.0; /* convert to K by default */
642 /* xfered_size = 'K'; was set above */
643 if(f_xfered
> 2048.0) {
646 if(f_xfered
> 2048.0) {
649 /* I should seriously hope that archlinux packages never break
650 * the 9999.9GB mark... we'd have more serious problems than the progress
655 /* 1 space + filenamelen + 1 space + 7 for size + 1 + 7 for rate + 2 for /s + 1 space + 8 for eta */
656 printf(" %ls%-*s %6.1f%c %#6.1f%c/s %02u:%02u:%02u", wcfname
,
657 padwid
, "", f_xfered
, xfered_size
,
658 rate
, rate_size
, eta_h
, eta_m
, eta_s
);
664 fill_progress(file_percent
, total_percent
, getcols() - infolen
);
666 fill_progress(file_percent
, file_percent
, getcols() - infolen
);
671 /* Callback to handle notifications from the library */
672 void cb_log(pmloglevel_t level
, char *fmt
, va_list args
)
674 if(!fmt
|| strlen(fmt
) == 0) {
680 pm_vasprintf(&string
, level
, fmt
, args
);
682 output
= alpm_list_add(output
, string
);
685 pm_vfprintf(stdout
, level
, fmt
, args
);
689 /* vim: set ts=2 sw=2 noet: */