Add interactive provider selection
[pacman-ng.git] / src / pacman / callback.c
blob43c56d008affc5d23a00626e851c19065bcd5535
1 /*
2 * callback.c
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/>.
21 #include "config.h"
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <sys/time.h>
27 #include <sys/types.h> /* off_t */
28 #include <unistd.h>
29 #include <wchar.h>
31 #include <alpm.h>
33 /* pacman */
34 #include "callback.h"
35 #include "util.h"
36 #include "conf.h"
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)
60 double retval = 0.0;
61 static struct timeval last_time = {0, 0};
63 /* on first call, simply set the last time and return */
64 if(first_call) {
65 gettimeofday(&last_time, NULL);
66 } else {
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) {
78 retval = 0.0;
79 } else {
80 last_time = this_time;
84 return(retval);
87 /* refactored from cb_trans_progress */
88 static void fill_progress(const int bar_percent, const int disp_percent,
89 const int proglen)
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;
95 int i;
97 if(bar_percent == 0) {
98 lasthash = 0;
99 mouth = 0;
102 if(hashlen > 0) {
103 printf(" [");
104 for(i = hashlen; i > 0; --i) {
105 /* if special progress bar enabled */
106 if(config->chomp) {
107 if(i > hashlen - hash) {
108 printf("-");
109 } else if(i == hashlen - hash) {
110 if(lasthash == hash) {
111 if(mouth) {
112 printf("\033[1;33mC\033[m");
113 } else {
114 printf("\033[1;33mc\033[m");
116 } else {
117 lasthash = hash;
118 mouth = mouth == 1 ? 0 : 1;
119 if(mouth) {
120 printf("\033[1;33mC\033[m");
121 } else {
122 printf("\033[1;33mc\033[m");
125 } else if(i%3 == 0) {
126 printf("\033[0;37mo\033[m");
127 } else {
128 printf("\033[0;37m \033[m");
130 } /* else regular progress bar */
131 else if(i > hashlen - hash) {
132 printf("#");
133 } else {
134 printf("-");
137 printf("]");
139 /* print display percent after progress bar */
140 /* 5 = 1 space + 3 digits + 1 % */
141 if(proglen >= 5) {
142 printf(" %3d%%", disp_percent);
145 if(bar_percent == 100) {
146 printf("\n");
147 } else {
148 printf("\r");
150 fflush(stdout);
155 /* callback to handle messages/notifications from libalpm transactions */
156 void cb_trans_evt(pmtransevt_t event, void *data1, void *data2)
158 switch(event) {
159 case PM_TRANS_EVT_CHECKDEPS_START:
160 printf(_("checking dependencies...\n"));
161 break;
162 case PM_TRANS_EVT_FILECONFLICTS_START:
163 if(config->noprogressbar) {
164 printf(_("checking for file conflicts...\n"));
166 break;
167 case PM_TRANS_EVT_RESOLVEDEPS_START:
168 printf(_("resolving dependencies...\n"));
169 break;
170 case PM_TRANS_EVT_INTERCONFLICTS_START:
171 printf(_("looking for inter-conflicts...\n"));
172 break;
173 case PM_TRANS_EVT_ADD_START:
174 if(config->noprogressbar) {
175 printf(_("installing %s...\n"), alpm_pkg_get_name(data1));
177 break;
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);
183 break;
184 case PM_TRANS_EVT_REMOVE_START:
185 if(config->noprogressbar) {
186 printf(_("removing %s...\n"), alpm_pkg_get_name(data1));
188 break;
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));
193 break;
194 case PM_TRANS_EVT_UPGRADE_START:
195 if(config->noprogressbar) {
196 printf(_("upgrading %s...\n"), alpm_pkg_get_name(data1));
198 break;
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);
205 break;
206 case PM_TRANS_EVT_INTEGRITY_START:
207 if(config->noprogressbar) {
208 printf(_("checking package integrity...\n"));
210 break;
211 case PM_TRANS_EVT_DELTA_INTEGRITY_START:
212 printf(_("checking delta integrity...\n"));
213 break;
214 case PM_TRANS_EVT_DELTA_PATCHES_START:
215 printf(_("applying deltas...\n"));
216 break;
217 case PM_TRANS_EVT_DELTA_PATCH_START:
218 printf(_("generating %s with %s... "), (char *)data1, (char *)data2);
219 break;
220 case PM_TRANS_EVT_DELTA_PATCH_DONE:
221 printf(_("success!\n"));
222 break;
223 case PM_TRANS_EVT_DELTA_PATCH_FAILED:
224 printf(_("failed.\n"));
225 break;
226 case PM_TRANS_EVT_SCRIPTLET_INFO:
227 printf("%s", (char*)data1);
228 break;
229 case PM_TRANS_EVT_RETRIEVE_START:
230 printf(_(":: Retrieving packages from %s...\n"), (char*)data1);
231 break;
232 case PM_TRANS_EVT_DISKSPACE_START:
233 if(config->noprogressbar) {
234 printf(_("checking available disk space...\n"));
236 break;
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:
246 /* nothing */
247 break;
249 fflush(stdout);
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)
257 switch(event) {
258 case PM_TRANS_CONV_INSTALL_IGNOREPKG:
259 *response = yesno(_(":: %s is in IgnorePkg/IgnoreGroup. Install anyway?"),
260 alpm_pkg_get_name(data1));
261 break;
262 case PM_TRANS_CONV_REPLACE_PKG:
263 *response = yesno(_(":: Replace %s with %s/%s?"),
264 alpm_pkg_get_name(data1),
265 (char *)data3,
266 alpm_pkg_get_name(data2));
267 break;
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?"),
273 (char *)data1,
274 (char *)data2,
275 (char *)data2);
276 } else {
277 *response = noyes(_(":: %s and %s are in conflict (%s). Remove %s?"),
278 (char *)data1,
279 (char *)data2,
280 (char *)data3,
281 (char *)data2);
283 break;
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));
292 printf(_n(
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);
297 printf("\n");
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);
304 break;
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,
311 depstring);
312 free(depstring);
313 select_display(providers);
314 printf("\n");
315 *response = select_question(count);
317 break;
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));
323 } else {
324 *response = 1;
326 break;
327 case PM_TRANS_CONV_CORRUPTED_PKG:
328 *response = yesno(_(":: File %s is corrupted. Do you want to delete it?"),
329 (char *)data1);
330 break;
332 if(config->noask) {
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) */
345 int infolen;
346 int digits, textlen;
347 size_t tmp;
348 char *opr = NULL;
349 /* used for wide character width determination and printing */
350 int len, wclen, wcwid, padwid;
351 wchar_t *wcstr;
353 if(config->noprogressbar) {
354 return;
357 if(percent == 0) {
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) {
363 return;
365 } else {
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. */
369 return;
373 prevpercent = percent;
375 /* set text of message to display */
376 switch (event) {
377 case PM_TRANS_PROGRESS_ADD_START:
378 opr = _("installing");
379 break;
380 case PM_TRANS_PROGRESS_UPGRADE_START:
381 opr = _("upgrading");
382 break;
383 case PM_TRANS_PROGRESS_REMOVE_START:
384 opr = _("removing");
385 break;
386 case PM_TRANS_PROGRESS_CONFLICTS_START:
387 opr = _("checking for file conflicts");
388 break;
389 case PM_TRANS_PROGRESS_DISKSPACE_START:
390 opr = _("checking available disk space");
391 break;
392 case PM_TRANS_PROGRESS_INTEGRITY_START:
393 opr = _("checking package integrity");
394 break;
395 default:
396 return;
399 infolen = getcols() * 6 / 10;
400 if (infolen < 50) {
401 infolen = 50;
404 /* find # of digits in package counts to scale output */
405 digits = 1;
406 tmp = howmany;
407 while((tmp /= 10)) {
408 ++digits;
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);
424 #else
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);
432 #endif
433 wcwid = wcswidth(wcstr, wclen);
434 padwid = textlen - wcwid;
435 /* if padwid is < 0, we need to trim the string so padwid = 0 */
436 if(padwid < 0) {
437 int i = textlen - 3;
438 wchar_t *p = wcstr;
439 /* grab the max number of char columns we can fill */
440 while(i > 0 && wcwidth(*p) < i) {
441 i -= wcwidth(*p);
442 p++;
444 /* then add the ellipsis and fill out any extra padding */
445 wcscpy(p, L"...");
446 padwid = i;
450 printf("(%*ld/%*ld) %ls%-*s", digits, (unsigned long)current,
451 digits, (unsigned long)howmany, wcstr, padwid, "");
453 free(wcstr);
455 /* call refactored fill progress function */
456 fill_progress(percent, percent, getcols() - infolen);
458 if(percent == 100) {
459 alpm_list_t *i = NULL;
460 on_progress = 0;
461 for(i = output; i; i = i->next) {
462 printf("%s", (char *)i->data);
464 fflush(stdout);
465 FREELIST(output);
466 } else {
467 on_progress = 1;
471 /* callback to handle receipt of total download value */
472 void cb_dl_total(off_t total)
474 list_total = total;
475 /* if we get a 0 value, it means this list has finished downloading,
476 * so clear out our list_xfered as well */
477 if(total == 0) {
478 list_xfered = 0;
482 /* callback to handle display of download progress */
483 void cb_dl_progress(const char *filename, off_t file_xfered, off_t file_total)
485 int infolen;
486 int filenamelen;
487 char *fname, *p;
488 /* used for wide character width determination and printing */
489 int len, wclen, wcwid, padwid;
490 wchar_t *wcfname;
492 int totaldownload = 0;
493 off_t xfered, total;
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);
502 fflush(stdout);
504 return;
507 infolen = getcols() * 6 / 10;
508 if (infolen < 50) {
509 infolen = 50;
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) {
516 /* sanity check */
517 if(list_xfered + file_total <= list_total) {
518 totaldownload = 1;
519 } else {
520 /* bogus values : don't enable totaldownload and reset */
521 list_xfered = 0;
522 list_total = 0;
526 if(totaldownload) {
527 xfered = list_xfered + file_xfered;
528 total = list_total;
529 } else {
530 xfered = file_xfered;
531 total = file_total;
534 /* bogus values : stop here */
535 if(xfered > total) {
536 return;
539 /* this is basically a switch on xfered: 0, total, and
540 * anything else */
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;
547 rate_last = 0.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(&current_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);
563 } else {
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 */
569 return;
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);
575 rate_last = rate;
576 xfered_last = xfered;
579 file_percent = (file_xfered * 100) / file_total;
581 if(totaldownload) {
582 total_percent = ((list_xfered + file_xfered) * 100) /
583 list_total;
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;
594 eta_m = eta_s / 60;
595 eta_s -= eta_m * 60;
597 fname = strdup(filename);
598 /* strip package or DB extension for cleaner look */
599 if((p = strstr(fname, ".pkg")) || (p = strstr(fname, ".db"))) {
600 *p = '\0';
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 */
614 if(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) {
619 i -= wcwidth(*p);
620 p++;
622 /* then add the ellipsis and fill out any extra padding */
623 wcscpy(p, L"...");
624 padwid = i;
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 */
631 if(rate > 2048.0) {
632 rate /= 1024.0;
633 rate_size = 'M';
634 if(rate > 2048.0) {
635 rate /= 1024.0;
636 rate_size = 'G';
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) {
644 f_xfered /= 1024.0;
645 xfered_size = 'M';
646 if(f_xfered > 2048.0) {
647 f_xfered /= 1024.0;
648 xfered_size = 'G';
649 /* I should seriously hope that archlinux packages never break
650 * the 9999.9GB mark... we'd have more serious problems than the progress
651 * bar in pacman */
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);
660 free(fname);
661 free(wcfname);
663 if(totaldownload) {
664 fill_progress(file_percent, total_percent, getcols() - infolen);
665 } else {
666 fill_progress(file_percent, file_percent, getcols() - infolen);
668 return;
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) {
675 return;
678 if(on_progress) {
679 char *string = NULL;
680 pm_vasprintf(&string, level, fmt, args);
681 if(string != NULL) {
682 output = alpm_list_add(output, string);
684 } else {
685 pm_vfprintf(stdout, level, fmt, args);
689 /* vim: set ts=2 sw=2 noet: */