Explicitly test time difference is greater than zero
[pacman-ng.git] / src / pacman / callback.c
blobc8f604fc73959ea05667362e322853b28c99f615
1 /*
2 * callback.c
4 * Copyright (c) 2006-2010 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 float 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 float get_update_timediff(int first_call)
60 float 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 float 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;
81 /* printf("\nupdate retval: %f\n", retval); DEBUG*/
85 return(retval);
88 /* refactored from cb_trans_progress */
89 static void fill_progress(const int bar_percent, const int disp_percent,
90 const int proglen)
92 /* 8 = 1 space + 1 [ + 1 ] + 5 for percent */
93 const int hashlen = proglen - 8;
94 const int hash = bar_percent * hashlen / 100;
95 static int lasthash = 0, mouth = 0;
96 int i;
98 if(bar_percent == 0) {
99 lasthash = 0;
100 mouth = 0;
103 if(hashlen > 0) {
104 printf(" [");
105 for(i = hashlen; i > 0; --i) {
106 /* if special progress bar enabled */
107 if(config->chomp) {
108 if(i > hashlen - hash) {
109 printf("-");
110 } else if(i == hashlen - hash) {
111 if(lasthash == hash) {
112 if(mouth) {
113 printf("\033[1;33mC\033[m");
114 } else {
115 printf("\033[1;33mc\033[m");
117 } else {
118 lasthash = hash;
119 mouth = mouth == 1 ? 0 : 1;
120 if(mouth) {
121 printf("\033[1;33mC\033[m");
122 } else {
123 printf("\033[1;33mc\033[m");
126 } else if(i%3 == 0) {
127 printf("\033[0;37mo\033[m");
128 } else {
129 printf("\033[0;37m \033[m");
131 } /* else regular progress bar */
132 else if(i > hashlen - hash) {
133 printf("#");
134 } else {
135 printf("-");
138 printf("]");
140 /* print display percent after progress bar */
141 /* 5 = 1 space + 3 digits + 1 % */
142 if(proglen >= 5) {
143 printf(" %3d%%", disp_percent);
146 if(bar_percent == 100) {
147 printf("\n");
148 } else {
149 printf("\r");
151 fflush(stdout);
156 /* callback to handle messages/notifications from libalpm transactions */
157 void cb_trans_evt(pmtransevt_t event, void *data1, void *data2)
159 switch(event) {
160 case PM_TRANS_EVT_CHECKDEPS_START:
161 printf(_("checking dependencies...\n"));
162 break;
163 case PM_TRANS_EVT_FILECONFLICTS_START:
164 if(config->noprogressbar) {
165 printf(_("checking for file conflicts...\n"));
167 break;
168 case PM_TRANS_EVT_RESOLVEDEPS_START:
169 printf(_("resolving dependencies...\n"));
170 break;
171 case PM_TRANS_EVT_INTERCONFLICTS_START:
172 printf(_("looking for inter-conflicts...\n"));
173 break;
174 case PM_TRANS_EVT_ADD_START:
175 if(config->noprogressbar) {
176 printf(_("installing %s...\n"), alpm_pkg_get_name(data1));
178 break;
179 case PM_TRANS_EVT_ADD_DONE:
180 alpm_logaction("installed %s (%s)\n",
181 alpm_pkg_get_name(data1),
182 alpm_pkg_get_version(data1));
183 display_optdepends(data1);
184 break;
185 case PM_TRANS_EVT_REMOVE_START:
186 if(config->noprogressbar) {
187 printf(_("removing %s...\n"), alpm_pkg_get_name(data1));
189 break;
190 case PM_TRANS_EVT_REMOVE_DONE:
191 alpm_logaction("removed %s (%s)\n",
192 alpm_pkg_get_name(data1),
193 alpm_pkg_get_version(data1));
194 break;
195 case PM_TRANS_EVT_UPGRADE_START:
196 if(config->noprogressbar) {
197 printf(_("upgrading %s...\n"), alpm_pkg_get_name(data1));
199 break;
200 case PM_TRANS_EVT_UPGRADE_DONE:
201 alpm_logaction("upgraded %s (%s -> %s)\n",
202 (char *)alpm_pkg_get_name(data1),
203 (char *)alpm_pkg_get_version(data2),
204 (char *)alpm_pkg_get_version(data1));
205 display_new_optdepends(data2,data1);
206 break;
207 case PM_TRANS_EVT_INTEGRITY_START:
208 printf(_("checking package integrity...\n"));
209 break;
210 case PM_TRANS_EVT_DELTA_INTEGRITY_START:
211 printf(_("checking delta integrity...\n"));
212 break;
213 case PM_TRANS_EVT_DELTA_PATCHES_START:
214 printf(_("applying deltas...\n"));
215 break;
216 case PM_TRANS_EVT_DELTA_PATCH_START:
217 printf(_("generating %s with %s... "), (char *)data1, (char *)data2);
218 break;
219 case PM_TRANS_EVT_DELTA_PATCH_DONE:
220 printf(_("success!\n"));
221 break;
222 case PM_TRANS_EVT_DELTA_PATCH_FAILED:
223 printf(_("failed.\n"));
224 break;
225 case PM_TRANS_EVT_SCRIPTLET_INFO:
226 printf("%s", (char*)data1);
227 break;
228 case PM_TRANS_EVT_RETRIEVE_START:
229 printf(_(":: Retrieving packages from %s...\n"), (char*)data1);
230 break;
231 case PM_TRANS_EVT_DISKSPACE_START:
232 if(config->noprogressbar) {
233 printf(_("checking available disk space...\n"));
235 break;
236 /* all the simple done events, with fallthrough for each */
237 case PM_TRANS_EVT_FILECONFLICTS_DONE:
238 case PM_TRANS_EVT_CHECKDEPS_DONE:
239 case PM_TRANS_EVT_RESOLVEDEPS_DONE:
240 case PM_TRANS_EVT_INTERCONFLICTS_DONE:
241 case PM_TRANS_EVT_INTEGRITY_DONE:
242 case PM_TRANS_EVT_DELTA_INTEGRITY_DONE:
243 case PM_TRANS_EVT_DELTA_PATCHES_DONE:
244 case PM_TRANS_EVT_DISKSPACE_DONE:
245 /* nothing */
246 break;
248 fflush(stdout);
251 /* callback to handle questions from libalpm transactions (yes/no) */
252 /* TODO this is one of the worst ever functions written. void *data ? wtf */
253 void cb_trans_conv(pmtransconv_t event, void *data1, void *data2,
254 void *data3, int *response)
256 switch(event) {
257 case PM_TRANS_CONV_INSTALL_IGNOREPKG:
258 *response = yesno(_(":: %s is in IgnorePkg/IgnoreGroup. Install anyway?"),
259 alpm_pkg_get_name(data1));
260 break;
261 case PM_TRANS_CONV_REPLACE_PKG:
262 *response = yesno(_(":: Replace %s with %s/%s?"),
263 alpm_pkg_get_name(data1),
264 (char *)data3,
265 alpm_pkg_get_name(data2));
266 break;
267 case PM_TRANS_CONV_CONFLICT_PKG:
268 /* data parameters: target package, local package, conflict (strings) */
269 /* print conflict only if it contains new information */
270 if(strcmp(data1, data3) == 0 || strcmp(data2, data3) == 0) {
271 *response = noyes(_(":: %s and %s are in conflict. Remove %s?"),
272 (char *)data1,
273 (char *)data2,
274 (char *)data2);
275 } else {
276 *response = noyes(_(":: %s and %s are in conflict (%s). Remove %s?"),
277 (char *)data1,
278 (char *)data2,
279 (char *)data3,
280 (char *)data2);
282 break;
283 case PM_TRANS_CONV_REMOVE_PKGS:
285 alpm_list_t *unresolved = (alpm_list_t *) data1;
286 alpm_list_t *namelist = NULL, *i;
287 for (i = unresolved; i; i = i->next) {
288 namelist = alpm_list_add(namelist,
289 (char *)alpm_pkg_get_name(i->data));
291 printf(_n(
292 ":: The following package cannot be upgraded due to unresolvable dependencies:\n",
293 ":: The following packages cannot be upgraded due to unresolvable dependencies:\n",
294 alpm_list_count(namelist)));
295 list_display(" ", namelist);
296 printf("\n");
297 *response = noyes(_n(
298 "Do you want to skip the above package for this upgrade?",
299 "Do you want to skip the above packages for this upgrade?",
300 alpm_list_count(namelist)));
301 alpm_list_free(namelist);
303 break;
304 case PM_TRANS_CONV_LOCAL_NEWER:
305 if(!config->op_s_downloadonly) {
306 *response = yesno(_(":: %s-%s: local version is newer. Upgrade anyway?"),
307 alpm_pkg_get_name(data1),
308 alpm_pkg_get_version(data1));
309 } else {
310 *response = 1;
312 break;
313 case PM_TRANS_CONV_CORRUPTED_PKG:
314 *response = yesno(_(":: File %s is corrupted. Do you want to delete it?"),
315 (char *)data1);
316 break;
318 if(config->noask) {
319 if(config->ask & event) {
320 /* inverse the default answer */
321 *response = !*response;
326 /* callback to handle display of transaction progress */
327 void cb_trans_progress(pmtransprog_t event, const char *pkgname, int percent,
328 int howmany, int remain)
330 float timediff;
332 /* size of line to allocate for text printing (e.g. not progressbar) */
333 int infolen;
334 int tmp, digits, textlen;
335 char *opr = NULL;
336 /* used for wide character width determination and printing */
337 int len, wclen, wcwid, padwid;
338 wchar_t *wcstr;
340 if(config->noprogressbar) {
341 return;
344 infolen = getcols() * 6 / 10;
345 if (infolen < 50) {
346 infolen = 50;
349 if(percent == 0) {
350 timediff = get_update_timediff(1);
351 } else {
352 timediff = get_update_timediff(0);
355 if(percent > 0 && percent < 100 && timediff > 0) {
356 /* only update the progress bar when
357 * a) we first start
358 * b) we end the progress
359 * c) it has been long enough since the last call
361 return;
364 /* if no pkgname, percent is too high or unchanged, then return */
365 if(!pkgname || percent == prevpercent) {
366 return;
369 prevpercent=percent;
370 /* set text of message to display */
371 switch (event) {
372 case PM_TRANS_PROGRESS_ADD_START:
373 opr = _("installing");
374 break;
375 case PM_TRANS_PROGRESS_UPGRADE_START:
376 opr = _("upgrading");
377 break;
378 case PM_TRANS_PROGRESS_REMOVE_START:
379 opr = _("removing");
380 break;
381 case PM_TRANS_PROGRESS_CONFLICTS_START:
382 opr = _("checking for file conflicts");
383 break;
384 case PM_TRANS_PROGRESS_DISKSPACE_START:
385 opr = _("checking available disk space");
386 break;
387 default:
388 return;
391 /* find # of digits in package counts to scale output */
392 digits = 1;
393 tmp = howmany;
394 while((tmp /= 10)) {
395 ++digits;
397 /* determine room left for non-digits text [not ( 1/12) part] */
398 textlen = infolen - 3 /* (/) */ - (2 * digits) - 1 /* space */;
400 /* In order to deal with characters from all locales, we have to worry
401 * about wide characters and their column widths. A lot of stuff is
402 * done here to figure out the actual number of screen columns used
403 * by the output, and then pad it accordingly so we fill the terminal.
405 /* len = opr len + pkgname len (if available) + space + null */
406 len = strlen(opr) + ((pkgname) ? strlen(pkgname) : 0) + 2;
407 wcstr = calloc(len, sizeof(wchar_t));
408 /* print our strings to the alloc'ed memory */
409 #if defined(HAVE_SWPRINTF)
410 wclen = swprintf(wcstr, len, L"%s %s", opr, pkgname);
411 #else
412 /* because the format string was simple, we can easily do this without
413 * using swprintf, although it is probably not as safe/fast. The max
414 * chars we can copy is decremented each time by subtracting the length
415 * of the already printed/copied wide char string. */
416 wclen = mbstowcs(wcstr, opr, len);
417 wclen += mbstowcs(wcstr + wclen, " ", len - wclen);
418 wclen += mbstowcs(wcstr + wclen, pkgname, len - wclen);
419 #endif
420 wcwid = wcswidth(wcstr, wclen);
421 padwid = textlen - wcwid;
422 /* if padwid is < 0, we need to trim the string so padwid = 0 */
423 if(padwid < 0) {
424 int i = textlen - 3;
425 wchar_t *p = wcstr;
426 /* grab the max number of char columns we can fill */
427 while(i > 0 && wcwidth(*p) < i) {
428 i -= wcwidth(*p);
429 p++;
431 /* then add the ellipsis and fill out any extra padding */
432 wcscpy(p, L"...");
433 padwid = i;
437 printf("(%*d/%*d) %ls%-*s", digits, remain, digits, howmany,
438 wcstr, padwid, "");
440 free(wcstr);
442 /* call refactored fill progress function */
443 fill_progress(percent, percent, getcols() - infolen);
445 if(percent == 100) {
446 alpm_list_t *i = NULL;
447 on_progress = 0;
448 for(i = output; i; i = i->next) {
449 printf("%s", (char *)i->data);
451 fflush(stdout);
452 FREELIST(output);
453 } else {
454 on_progress = 1;
458 /* callback to handle receipt of total download value */
459 void cb_dl_total(off_t total)
461 list_total = total;
462 /* if we get a 0 value, it means this list has finished downloading,
463 * so clear out our list_xfered as well */
464 if(total == 0) {
465 list_xfered = 0;
469 /* callback to handle display of download progress */
470 void cb_dl_progress(const char *filename, off_t file_xfered, off_t file_total)
472 int infolen;
473 int filenamelen;
474 char *fname, *p;
475 /* used for wide character width determination and printing */
476 int len, wclen, wcwid, padwid;
477 wchar_t *wcfname;
479 int totaldownload = 0;
480 off_t xfered, total;
481 float rate = 0.0, timediff = 0.0, f_xfered = 0.0;
482 unsigned int eta_h = 0, eta_m = 0, eta_s = 0;
483 int file_percent = 0, total_percent = 0;
484 char rate_size = 'K', xfered_size = 'K';
486 if(config->noprogressbar || file_total == -1) {
487 if(file_xfered == 0) {
488 printf(_("downloading %s...\n"), filename);
489 fflush(stdout);
491 return;
494 infolen = getcols() * 6 / 10;
495 if (infolen < 50) {
496 infolen = 50;
498 /* explanation of magic 28 number at the end */
499 filenamelen = infolen - 28;
501 /* only use TotalDownload if enabled and we have a callback value */
502 if(config->totaldownload && list_total) {
503 /* sanity check */
504 if(list_xfered + file_total <= list_total) {
505 totaldownload = 1;
506 } else {
507 /* bogus values : don't enable totaldownload and reset */
508 list_xfered = 0;
509 list_total = 0;
513 if(totaldownload) {
514 xfered = list_xfered + file_xfered;
515 total = list_total;
516 } else {
517 xfered = file_xfered;
518 total = file_total;
521 /* bogus values : stop here */
522 if(xfered > total) {
523 return;
526 /* this is basically a switch on xfered: 0, total, and
527 * anything else */
528 if(file_xfered == 0) {
529 /* set default starting values, ensure we only call this once
530 * if TotalDownload is enabled */
531 if(!totaldownload || (totaldownload && list_xfered == 0)) {
532 gettimeofday(&initial_time, NULL);
533 xfered_last = (off_t)0;
534 rate_last = 0.0;
535 get_update_timediff(1);
537 } else if(file_xfered == file_total) {
538 /* compute final values */
539 struct timeval current_time;
540 float diff_sec, diff_usec;
542 gettimeofday(&current_time, NULL);
543 diff_sec = current_time.tv_sec - initial_time.tv_sec;
544 diff_usec = current_time.tv_usec - initial_time.tv_usec;
545 timediff = diff_sec + (diff_usec / 1000000.0);
546 rate = xfered / (timediff * 1024.0);
548 /* round elapsed time to the nearest second */
549 eta_s = (int)(timediff + 0.5);
550 } else {
551 /* compute current average values */
552 timediff = get_update_timediff(0);
554 if(timediff < UPDATE_SPEED_SEC) {
555 /* return if the calling interval was too short */
556 return;
558 rate = (xfered - xfered_last) / (timediff * 1024.0);
559 /* average rate to reduce jumpiness */
560 rate = (rate + 2 * rate_last) / 3;
561 eta_s = (total - xfered) / (rate * 1024.0);
562 rate_last = rate;
563 xfered_last = xfered;
566 file_percent = (int)((float)file_xfered) / ((float)file_total) * 100;
568 if(totaldownload) {
569 total_percent = (int)((float)list_xfered + file_xfered) /
570 ((float)list_total) * 100;
572 /* if we are at the end, add the completed file to list_xfered */
573 if(file_xfered == file_total) {
574 list_xfered += file_total;
578 /* fix up time for display */
579 eta_h = eta_s / 3600;
580 eta_s -= eta_h * 3600;
581 eta_m = eta_s / 60;
582 eta_s -= eta_m * 60;
584 fname = strdup(filename);
585 /* strip package or DB extension for cleaner look */
586 if((p = strstr(fname, ".pkg")) || (p = strstr(fname, ".db"))) {
587 *p = '\0';
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.
594 /* len = filename len + null */
595 len = strlen(filename) + 1;
596 wcfname = calloc(len, sizeof(wchar_t));
597 wclen = mbstowcs(wcfname, fname, len);
598 wcwid = wcswidth(wcfname, wclen);
599 padwid = filenamelen - wcwid;
600 /* if padwid is < 0, we need to trim the string so padwid = 0 */
601 if(padwid < 0) {
602 int i = filenamelen - 3;
603 wchar_t *p = wcfname;
604 /* grab the max number of char columns we can fill */
605 while(i > 0 && wcwidth(*p) < i) {
606 i -= wcwidth(*p);
607 p++;
609 /* then add the ellipsis and fill out any extra padding */
610 wcscpy(p, L"...");
611 padwid = i;
615 /* Awesome formatting for progress bar. We need a mess of Kb->Mb->Gb stuff
616 * here. We'll use limit of 2048 for each until we get some empirical */
617 /* rate_size = 'K'; was set above */
618 if(rate > 2048.0) {
619 rate /= 1024.0;
620 rate_size = 'M';
621 if(rate > 2048.0) {
622 rate /= 1024.0;
623 rate_size = 'G';
624 /* we should not go higher than this for a few years (9999.9 Gb/s?)*/
628 f_xfered = xfered / 1024.0; /* convert to K by default */
629 /* xfered_size = 'K'; was set above */
630 if(f_xfered > 2048.0) {
631 f_xfered /= 1024.0;
632 xfered_size = 'M';
633 if(f_xfered > 2048.0) {
634 f_xfered /= 1024.0;
635 xfered_size = 'G';
636 /* I should seriously hope that archlinux packages never break
637 * the 9999.9GB mark... we'd have more serious problems than the progress
638 * bar in pacman */
642 /* 1 space + filenamelen + 1 space + 7 for size + 1 + 7 for rate + 2 for /s + 1 space + 8 for eta */
643 printf(" %ls%-*s %6.1f%c %#6.1f%c/s %02u:%02u:%02u", wcfname,
644 padwid, "", f_xfered, xfered_size,
645 rate, rate_size, eta_h, eta_m, eta_s);
647 free(fname);
648 free(wcfname);
650 if(totaldownload) {
651 fill_progress(file_percent, total_percent, getcols() - infolen);
652 } else {
653 fill_progress(file_percent, file_percent, getcols() - infolen);
655 return;
658 /* Callback to handle notifications from the library */
659 void cb_log(pmloglevel_t level, char *fmt, va_list args)
661 if(!fmt || strlen(fmt) == 0) {
662 return;
665 if(on_progress) {
666 char *string = NULL;
667 pm_vasprintf(&string, level, fmt, args);
668 if(string != NULL) {
669 output = alpm_list_add(output, string);
671 } else {
672 pm_vfprintf(stdout, level, fmt, args);
676 /* vim: set ts=2 sw=2 noet: */