libalpm
Arch Linux Package Manager Library
callback.c
Go to the documentation of this file.
00001 /*
00002  *  callback.c
00003  *
00004  *  Copyright (c) 2006-2011 Pacman Development Team <pacman-dev@archlinux.org>
00005  *  Copyright (c) 2002-2006 by Judd Vinet <jvinet@zeroflux.org>
00006  *
00007  *  This program is free software; you can redistribute it and/or modify
00008  *  it under the terms of the GNU General Public License as published by
00009  *  the Free Software Foundation; either version 2 of the License, or
00010  *  (at your option) any later version.
00011  *
00012  *  This program is distributed in the hope that it will be useful,
00013  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  *  GNU General Public License for more details.
00016  *
00017  *  You should have received a copy of the GNU General Public License
00018  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
00019  */
00020 
00021 #include <stdio.h>
00022 #include <stdlib.h>
00023 #include <string.h>
00024 #include <sys/time.h>
00025 #include <sys/types.h> /* off_t */
00026 #include <time.h>
00027 #include <unistd.h>
00028 #include <wchar.h>
00029 #include <limits.h> /* UINT_MAX */
00030 
00031 #include <alpm.h>
00032 
00033 /* pacman */
00034 #include "callback.h"
00035 #include "util.h"
00036 #include "conf.h"
00037 
00038 /* download progress bar */
00039 static off_t list_xfered = 0.0;
00040 static off_t list_total = 0.0;
00041 
00042 /* delayed output during progress bar */
00043 static int on_progress = 0;
00044 static alpm_list_t *output = NULL;
00045 
00046 /* update speed for the fill_progress based functions */
00047 #define UPDATE_SPEED_MS 200
00048 
00049 /**
00050  * Silly little helper function, determines if the caller needs a visual update
00051  * since the last time this function was called.
00052  * This is made for the two progress bar functions, to prevent flicker.
00053  * @param first_call 1 on first call for initialization purposes, 0 otherwise
00054  * @return number of milliseconds since last call
00055  */
00056 static long get_update_timediff(int first_call)
00057 {
00058     long retval = 0;
00059     static struct timeval last_time = {0, 0};
00060 
00061     /* on first call, simply set the last time and return */
00062     if(first_call) {
00063         gettimeofday(&last_time, NULL);
00064     } else {
00065         struct timeval this_time;
00066         time_t diff_sec;
00067         suseconds_t diff_usec;
00068 
00069         gettimeofday(&this_time, NULL);
00070         diff_sec = this_time.tv_sec - last_time.tv_sec;
00071         diff_usec = this_time.tv_usec - last_time.tv_usec;
00072 
00073         retval = (diff_sec * 1000) + (diff_usec / 1000);
00074 
00075         /* do not update last_time if interval was too short */
00076         if(retval >= UPDATE_SPEED_MS) {
00077             last_time = this_time;
00078         }
00079     }
00080 
00081     return retval;
00082 }
00083 
00084 /* refactored from cb_trans_progress */
00085 static void fill_progress(const int bar_percent, const int disp_percent,
00086         const int proglen)
00087 {
00088     /* 8 = 1 space + 1 [ + 1 ] + 5 for percent */
00089     const int hashlen = proglen - 8;
00090     const int hash = bar_percent * hashlen / 100;
00091     static int lasthash = 0, mouth = 0;
00092     int i;
00093 
00094     if(bar_percent == 0) {
00095         lasthash = 0;
00096         mouth = 0;
00097     }
00098 
00099     if(hashlen > 0) {
00100         fputs(" [", stdout);
00101         for(i = hashlen; i > 0; --i) {
00102             /* if special progress bar enabled */
00103             if(config->chomp) {
00104                 if(i > hashlen - hash) {
00105                     putchar('-');
00106                 } else if(i == hashlen - hash) {
00107                     if(lasthash == hash) {
00108                         if(mouth) {
00109                             fputs("\033[1;33mC\033[m", stdout);
00110                         } else {
00111                             fputs("\033[1;33mc\033[m", stdout);
00112                         }
00113                     } else {
00114                         lasthash = hash;
00115                         mouth = mouth == 1 ? 0 : 1;
00116                         if(mouth) {
00117                             fputs("\033[1;33mC\033[m", stdout);
00118                         } else {
00119                             fputs("\033[1;33mc\033[m", stdout);
00120                         }
00121                     }
00122                 } else if(i % 3 == 0) {
00123                     fputs("\033[0;37mo\033[m", stdout);
00124                 } else {
00125                     fputs("\033[0;37m \033[m", stdout);
00126                 }
00127             } /* else regular progress bar */
00128             else if(i > hashlen - hash) {
00129                 putchar('#');
00130             } else {
00131                 putchar('-');
00132             }
00133         }
00134         putchar(']');
00135     }
00136     /* print display percent after progress bar */
00137     /* 5 = 1 space + 3 digits + 1 % */
00138     if(proglen >= 5) {
00139         printf(" %3d%%", disp_percent);
00140     }
00141 
00142     if(bar_percent == 100) {
00143         putchar('\n');
00144     } else {
00145         putchar('\r');
00146     }
00147     fflush(stdout);
00148 }
00149 
00150 
00151 
00152 /* callback to handle messages/notifications from libalpm transactions */
00153 void cb_event(alpm_event_t event, void *data1, void *data2)
00154 {
00155     if(config->print) {
00156         return;
00157     }
00158     switch(event) {
00159         case ALPM_EVENT_CHECKDEPS_START:
00160           printf(_("checking dependencies...\n"));
00161             break;
00162         case ALPM_EVENT_FILECONFLICTS_START:
00163             if(config->noprogressbar) {
00164                 printf(_("checking for file conflicts...\n"));
00165             }
00166             break;
00167         case ALPM_EVENT_RESOLVEDEPS_START:
00168             printf(_("resolving dependencies...\n"));
00169             break;
00170         case ALPM_EVENT_INTERCONFLICTS_START:
00171             printf(_("looking for inter-conflicts...\n"));
00172             break;
00173         case ALPM_EVENT_ADD_START:
00174             if(config->noprogressbar) {
00175                 printf(_("installing %s...\n"), alpm_pkg_get_name(data1));
00176             }
00177             break;
00178         case ALPM_EVENT_ADD_DONE:
00179             alpm_logaction(config->handle, "installed %s (%s)\n",
00180                      alpm_pkg_get_name(data1),
00181                      alpm_pkg_get_version(data1));
00182             display_optdepends(data1);
00183             break;
00184         case ALPM_EVENT_REMOVE_START:
00185             if(config->noprogressbar) {
00186             printf(_("removing %s...\n"), alpm_pkg_get_name(data1));
00187             }
00188             break;
00189         case ALPM_EVENT_REMOVE_DONE:
00190             alpm_logaction(config->handle, "removed %s (%s)\n",
00191                      alpm_pkg_get_name(data1),
00192                      alpm_pkg_get_version(data1));
00193             break;
00194         case ALPM_EVENT_UPGRADE_START:
00195             if(config->noprogressbar) {
00196                 printf(_("upgrading %s...\n"), alpm_pkg_get_name(data1));
00197             }
00198             break;
00199         case ALPM_EVENT_UPGRADE_DONE:
00200             alpm_logaction(config->handle, "upgraded %s (%s -> %s)\n",
00201                      alpm_pkg_get_name(data1),
00202                      alpm_pkg_get_version(data2),
00203                      alpm_pkg_get_version(data1));
00204             display_new_optdepends(data2,data1);
00205             break;
00206         case ALPM_EVENT_INTEGRITY_START:
00207             if(config->noprogressbar) {
00208                 printf(_("checking package integrity...\n"));
00209             }
00210             break;
00211         case ALPM_EVENT_LOAD_START:
00212             if(config->noprogressbar) {
00213                 printf(_("loading package files...\n"));
00214             }
00215             break;
00216         case ALPM_EVENT_DELTA_INTEGRITY_START:
00217             printf(_("checking delta integrity...\n"));
00218             break;
00219         case ALPM_EVENT_DELTA_PATCHES_START:
00220             printf(_("applying deltas...\n"));
00221             break;
00222         case ALPM_EVENT_DELTA_PATCH_START:
00223             printf(_("generating %s with %s... "), (char *)data1, (char *)data2);
00224             break;
00225         case ALPM_EVENT_DELTA_PATCH_DONE:
00226             printf(_("success!\n"));
00227             break;
00228         case ALPM_EVENT_DELTA_PATCH_FAILED:
00229             printf(_("failed.\n"));
00230             break;
00231         case ALPM_EVENT_SCRIPTLET_INFO:
00232             fputs((const char *)data1, stdout);
00233             break;
00234         case ALPM_EVENT_RETRIEVE_START:
00235             printf(_(":: Retrieving packages ...\n"));
00236             break;
00237         case ALPM_EVENT_DISKSPACE_START:
00238             if(config->noprogressbar) {
00239                 printf(_("checking available disk space...\n"));
00240             }
00241             break;
00242         /* all the simple done events, with fallthrough for each */
00243         case ALPM_EVENT_FILECONFLICTS_DONE:
00244         case ALPM_EVENT_CHECKDEPS_DONE:
00245         case ALPM_EVENT_RESOLVEDEPS_DONE:
00246         case ALPM_EVENT_INTERCONFLICTS_DONE:
00247         case ALPM_EVENT_INTEGRITY_DONE:
00248         case ALPM_EVENT_LOAD_DONE:
00249         case ALPM_EVENT_DELTA_INTEGRITY_DONE:
00250         case ALPM_EVENT_DELTA_PATCHES_DONE:
00251         case ALPM_EVENT_DISKSPACE_DONE:
00252             /* nothing */
00253             break;
00254     }
00255     fflush(stdout);
00256 }
00257 
00258 /* callback to handle questions from libalpm transactions (yes/no) */
00259 /* TODO this is one of the worst ever functions written. void *data ? wtf */
00260 void cb_question(alpm_question_t event, void *data1, void *data2,
00261                    void *data3, int *response)
00262 {
00263     if(config->print) {
00264         return;
00265     }
00266     switch(event) {
00267         case ALPM_QUESTION_INSTALL_IGNOREPKG:
00268             if(!config->op_s_downloadonly) {
00269                 *response = yesno(_(":: %s is in IgnorePkg/IgnoreGroup. Install anyway?"),
00270                                   alpm_pkg_get_name(data1));
00271             } else {
00272                 *response = 1;
00273             }
00274             break;
00275         case ALPM_QUESTION_REPLACE_PKG:
00276             *response = yesno(_(":: Replace %s with %s/%s?"),
00277                     alpm_pkg_get_name(data1),
00278                     (char *)data3,
00279                     alpm_pkg_get_name(data2));
00280             break;
00281         case ALPM_QUESTION_CONFLICT_PKG:
00282             /* data parameters: target package, local package, conflict (strings) */
00283             /* print conflict only if it contains new information */
00284             if(strcmp(data1, data3) == 0 || strcmp(data2, data3) == 0) {
00285                 *response = noyes(_(":: %s and %s are in conflict. Remove %s?"),
00286                         (char *)data1,
00287                         (char *)data2,
00288                         (char *)data2);
00289             } else {
00290                 *response = noyes(_(":: %s and %s are in conflict (%s). Remove %s?"),
00291                         (char *)data1,
00292                         (char *)data2,
00293                         (char *)data3,
00294                         (char *)data2);
00295             }
00296             break;
00297         case ALPM_QUESTION_REMOVE_PKGS:
00298             {
00299                 alpm_list_t *unresolved = data1;
00300                 alpm_list_t *namelist = NULL, *i;
00301                 size_t count = 0;
00302                 for(i = unresolved; i; i = i->next) {
00303                     namelist = alpm_list_add(namelist,
00304                             (char *)alpm_pkg_get_name(i->data));
00305                     count++;
00306                 }
00307                 printf(_n(
00308                             ":: The following package cannot be upgraded due to unresolvable dependencies:\n",
00309                             ":: The following packages cannot be upgraded due to unresolvable dependencies:\n",
00310                             count));
00311                 list_display("     ", namelist);
00312                 printf("\n");
00313                 *response = noyes(_n(
00314                             "Do you want to skip the above package for this upgrade?",
00315                             "Do you want to skip the above packages for this upgrade?",
00316                             count));
00317                 alpm_list_free(namelist);
00318             }
00319             break;
00320         case ALPM_QUESTION_SELECT_PROVIDER:
00321             {
00322                 alpm_list_t *providers = data1;
00323                 size_t count = alpm_list_count(providers);
00324                 char *depstring = alpm_dep_compute_string((alpm_depend_t *)data2);
00325                 printf(_(":: There are %zd providers available for %s:\n"), count,
00326                         depstring);
00327                 free(depstring);
00328                 select_display(providers);
00329                 *response = select_question(count);
00330             }
00331             break;
00332         case ALPM_QUESTION_LOCAL_NEWER:
00333             if(!config->op_s_downloadonly) {
00334                 *response = yesno(_(":: %s-%s: local version is newer. Upgrade anyway?"),
00335                         alpm_pkg_get_name(data1),
00336                         alpm_pkg_get_version(data1));
00337             } else {
00338                 *response = 1;
00339             }
00340             break;
00341         case ALPM_QUESTION_CORRUPTED_PKG:
00342             *response = yesno(_(":: File %s is corrupted (%s).\n"
00343                         "Do you want to delete it?"),
00344                     (char *)data1,
00345                     alpm_strerror(*(alpm_errno_t *)data2));
00346             break;
00347         case ALPM_QUESTION_IMPORT_KEY:
00348             {
00349                 alpm_pgpkey_t *key = data1;
00350                 char created[12];
00351                 const char *revoked = "";
00352                 time_t time = (time_t)key->created;
00353                 strftime(created, 12, "%Y-%m-%d", localtime(&time));
00354 
00355                 if(key->revoked) {
00356                     revoked = " (revoked)";
00357                 }
00358 
00359                 *response = yesno(_(":: Import PGP key %d%c/%s, \"%s\", created: %s%s?"),
00360                         key->length, key->pubkey_algo, key->fingerprint, key->uid, created, revoked);
00361             }
00362             break;
00363     }
00364     if(config->noask) {
00365         if(config->ask & event) {
00366             /* inverse the default answer */
00367             *response = !*response;
00368         }
00369     }
00370 }
00371 
00372 /* callback to handle display of transaction progress */
00373 void cb_progress(alpm_progress_t event, const char *pkgname, int percent,
00374                        size_t howmany, size_t current)
00375 {
00376     static int prevpercent;
00377     static size_t prevcurrent;
00378     /* size of line to allocate for text printing (e.g. not progressbar) */
00379     int infolen;
00380     int digits, textlen;
00381     size_t tmp;
00382     char *opr = NULL;
00383     /* used for wide character width determination and printing */
00384     int len, wclen, wcwid, padwid;
00385     wchar_t *wcstr;
00386 
00387     const unsigned short cols = getcols();
00388 
00389     if(config->noprogressbar || cols == 0) {
00390         return;
00391     }
00392 
00393     if(percent == 0) {
00394         get_update_timediff(1);
00395     } else if(percent == 100) {
00396         /* no need for timediff update, but unconditionally continue unless we
00397          * already completed on a previous call */
00398         if(prevpercent == 100) {
00399             return;
00400         }
00401     } else {
00402         if(current != prevcurrent) {
00403             /* update always */
00404         } else if(!pkgname || percent == prevpercent ||
00405                 get_update_timediff(0) < UPDATE_SPEED_MS) {
00406             /* only update the progress bar when we have a package name, the
00407              * percentage has changed, and it has been long enough. */
00408             return;
00409         }
00410     }
00411 
00412     prevpercent = percent;
00413     prevcurrent = current;
00414 
00415     /* set text of message to display */
00416     switch (event) {
00417         case ALPM_PROGRESS_ADD_START:
00418             opr = _("installing");
00419             break;
00420         case ALPM_PROGRESS_UPGRADE_START:
00421             opr = _("upgrading");
00422             break;
00423         case ALPM_PROGRESS_REMOVE_START:
00424             opr = _("removing");
00425             break;
00426         case ALPM_PROGRESS_CONFLICTS_START:
00427             opr = _("checking for file conflicts");
00428             break;
00429         case ALPM_PROGRESS_DISKSPACE_START:
00430             opr = _("checking available disk space");
00431             break;
00432         case ALPM_PROGRESS_INTEGRITY_START:
00433             opr = _("checking package integrity");
00434             break;
00435         case ALPM_PROGRESS_LOAD_START:
00436             opr = _("loading package files");
00437             break;
00438         default:
00439             return;
00440     }
00441 
00442     infolen = cols * 6 / 10;
00443     if(infolen < 50) {
00444         infolen = 50;
00445     }
00446 
00447     /* find # of digits in package counts to scale output */
00448     digits = 1;
00449     tmp = howmany;
00450     while((tmp /= 10)) {
00451         ++digits;
00452     }
00453     /* determine room left for non-digits text [not ( 1/12) part] */
00454     textlen = infolen - 3 /* (/) */ - (2 * digits) - 1 /* space */;
00455 
00456     /* In order to deal with characters from all locales, we have to worry
00457      * about wide characters and their column widths. A lot of stuff is
00458      * done here to figure out the actual number of screen columns used
00459      * by the output, and then pad it accordingly so we fill the terminal.
00460      */
00461     /* len = opr len + pkgname len (if available) + space + null */
00462     len = strlen(opr) + ((pkgname) ? strlen(pkgname) : 0) + 2;
00463     wcstr = calloc(len, sizeof(wchar_t));
00464     /* print our strings to the alloc'ed memory */
00465 #if defined(HAVE_SWPRINTF)
00466     wclen = swprintf(wcstr, len, L"%s %s", opr, pkgname);
00467 #else
00468     /* because the format string was simple, we can easily do this without
00469      * using swprintf, although it is probably not as safe/fast. The max
00470      * chars we can copy is decremented each time by subtracting the length
00471      * of the already printed/copied wide char string. */
00472     wclen = mbstowcs(wcstr, opr, len);
00473     wclen += mbstowcs(wcstr + wclen, " ", len - wclen);
00474     wclen += mbstowcs(wcstr + wclen, pkgname, len - wclen);
00475 #endif
00476     wcwid = wcswidth(wcstr, wclen);
00477     padwid = textlen - wcwid;
00478     /* if padwid is < 0, we need to trim the string so padwid = 0 */
00479     if(padwid < 0) {
00480         int i = textlen - 3;
00481         wchar_t *p = wcstr;
00482         /* grab the max number of char columns we can fill */
00483         while(i > 0 && wcwidth(*p) < i) {
00484             i -= wcwidth(*p);
00485             p++;
00486         }
00487         /* then add the ellipsis and fill out any extra padding */
00488         wcscpy(p, L"...");
00489         padwid = i;
00490 
00491     }
00492 
00493     printf("(%*ld/%*ld) %ls%-*s", digits, (unsigned long)current,
00494             digits, (unsigned long)howmany, wcstr, padwid, "");
00495 
00496     free(wcstr);
00497 
00498     /* call refactored fill progress function */
00499     fill_progress(percent, percent, cols - infolen);
00500 
00501     if(percent == 100) {
00502         alpm_list_t *i = NULL;
00503         on_progress = 0;
00504         for(i = output; i; i = i->next) {
00505             fputs((const char *)i->data, stdout);
00506         }
00507         fflush(stdout);
00508         FREELIST(output);
00509     } else {
00510         on_progress = 1;
00511     }
00512 }
00513 
00514 /* callback to handle receipt of total download value */
00515 void cb_dl_total(off_t total)
00516 {
00517     list_total = total;
00518     /* if we get a 0 value, it means this list has finished downloading,
00519      * so clear out our list_xfered as well */
00520     if(total == 0) {
00521         list_xfered = 0;
00522     }
00523 }
00524 
00525 /* callback to handle display of download progress */
00526 void cb_dl_progress(const char *filename, off_t file_xfered, off_t file_total)
00527 {
00528     static double rate_last;
00529     static off_t xfered_last;
00530     static struct timeval initial_time;
00531     int infolen;
00532     int filenamelen;
00533     char *fname, *p;
00534     /* used for wide character width determination and printing */
00535     int len, wclen, wcwid, padwid;
00536     wchar_t *wcfname;
00537 
00538     int totaldownload = 0;
00539     off_t xfered, total;
00540     double rate = 0.0;
00541     long timediff = 0;
00542     unsigned int eta_h = 0, eta_m = 0, eta_s = 0;
00543     double rate_human, xfered_human;
00544     const char *rate_label, *xfered_label;
00545     int file_percent = 0, total_percent = 0;
00546 
00547     const unsigned short cols = getcols();
00548 
00549     if(config->noprogressbar || cols == 0 || file_total == -1) {
00550         if(file_xfered == 0) {
00551             printf(_("downloading %s...\n"), filename);
00552             fflush(stdout);
00553         }
00554         return;
00555     }
00556 
00557     infolen = cols * 6 / 10;
00558     if(infolen < 50) {
00559         infolen = 50;
00560     }
00561     /* only use TotalDownload if enabled and we have a callback value */
00562     if(config->totaldownload && list_total) {
00563         /* sanity check */
00564         if(list_xfered + file_total <= list_total) {
00565             totaldownload = 1;
00566         } else {
00567             /* bogus values : don't enable totaldownload and reset */
00568             list_xfered = 0;
00569             list_total = 0;
00570         }
00571     }
00572 
00573     if(totaldownload) {
00574         xfered = list_xfered + file_xfered;
00575         total = list_total;
00576     } else {
00577         xfered = file_xfered;
00578         total = file_total;
00579     }
00580 
00581     /* bogus values : stop here */
00582     if(xfered > total) {
00583         return;
00584     }
00585 
00586     /* this is basically a switch on xfered: 0, total, and
00587      * anything else */
00588     if(file_xfered == 0) {
00589         /* set default starting values, ensure we only call this once
00590          * if TotalDownload is enabled */
00591         if(!totaldownload || (totaldownload && list_xfered == 0)) {
00592             gettimeofday(&initial_time, NULL);
00593             xfered_last = (off_t)0;
00594             rate_last = 0.0;
00595             get_update_timediff(1);
00596         }
00597     } else if(file_xfered == file_total) {
00598         /* compute final values */
00599         struct timeval current_time;
00600         time_t diff_sec;
00601         suseconds_t diff_usec;
00602 
00603         gettimeofday(&current_time, NULL);
00604         diff_sec = current_time.tv_sec - initial_time.tv_sec;
00605         diff_usec = current_time.tv_usec - initial_time.tv_usec;
00606         timediff = (diff_sec * 1000) + (diff_usec / 1000);
00607         if(timediff > 0) {
00608             rate = (double)xfered / (timediff / 1000.0);
00609             /* round elapsed time (in ms) to the nearest second */
00610             eta_s = (unsigned int)(timediff + 500) / 1000;
00611         } else {
00612             eta_s = 0;
00613         }
00614     } else {
00615         /* compute current average values */
00616         timediff = get_update_timediff(0);
00617 
00618         if(timediff < UPDATE_SPEED_MS) {
00619             /* return if the calling interval was too short */
00620             return;
00621         }
00622         rate = (double)(xfered - xfered_last) / (timediff / 1000.0);
00623         /* average rate to reduce jumpiness */
00624         rate = (rate + 2 * rate_last) / 3;
00625         if(rate > 0.0) {
00626             eta_s = (total - xfered) / rate;
00627         } else {
00628             eta_s = UINT_MAX;
00629         }
00630         rate_last = rate;
00631         xfered_last = xfered;
00632     }
00633 
00634     if(file_total) {
00635         file_percent = (file_xfered * 100) / file_total;
00636     } else {
00637         file_percent = 100;
00638     }
00639 
00640     if(totaldownload) {
00641         total_percent = ((list_xfered + file_xfered) * 100) /
00642             list_total;
00643 
00644         /* if we are at the end, add the completed file to list_xfered */
00645         if(file_xfered == file_total) {
00646             list_xfered += file_total;
00647         }
00648     }
00649 
00650     /* fix up time for display */
00651     eta_h = eta_s / 3600;
00652     eta_s -= eta_h * 3600;
00653     eta_m = eta_s / 60;
00654     eta_s -= eta_m * 60;
00655 
00656     len = strlen(filename);
00657     fname = malloc(len + 1);
00658     memcpy(fname, filename, len);
00659     /* strip package or DB extension for cleaner look */
00660     if((p = strstr(fname, ".pkg")) || (p = strstr(fname, ".db"))) {
00661         /* tack on a .sig suffix for signatures */
00662         if(memcmp(&filename[len - 4], ".sig", 4) == 0) {
00663             memcpy(p, ".sig", 4);
00664 
00665             /* adjust length for later calculations */
00666             len = p - fname + 4;
00667         } else {
00668             len = p - fname;
00669         }
00670     }
00671     fname[len] = '\0';
00672 
00673     /* 1 space + filenamelen + 1 space + 6 for size + 1 space + 3 for label +
00674      * + 2 spaces + 4 for rate  + 1 for label + 2 for /s + 1 space +
00675      * 8 for eta, gives us the magic 30 */
00676     filenamelen = infolen - 30;
00677     /* see printf() code, we omit 'HH:' in these conditions */
00678     if(eta_h == 0 || eta_h >= 100) {
00679         filenamelen += 3;
00680     }
00681 
00682     /* In order to deal with characters from all locales, we have to worry
00683      * about wide characters and their column widths. A lot of stuff is
00684      * done here to figure out the actual number of screen columns used
00685      * by the output, and then pad it accordingly so we fill the terminal.
00686      */
00687     /* len = filename len + null */
00688     wcfname = calloc(len + 1, sizeof(wchar_t));
00689     wclen = mbstowcs(wcfname, fname, len);
00690     wcwid = wcswidth(wcfname, wclen);
00691     padwid = filenamelen - wcwid;
00692     /* if padwid is < 0, we need to trim the string so padwid = 0 */
00693     if(padwid < 0) {
00694         int i = filenamelen - 3;
00695         wchar_t *wcp = wcfname;
00696         /* grab the max number of char columns we can fill */
00697         while(i > 0 && wcwidth(*wcp) < i) {
00698             i -= wcwidth(*wcp);
00699             wcp++;
00700         }
00701         /* then add the ellipsis and fill out any extra padding */
00702         wcscpy(wcp, L"...");
00703         padwid = i;
00704 
00705     }
00706 
00707     rate_human = humanize_size((off_t)rate, '\0', &rate_label);
00708     xfered_human = humanize_size(xfered, '\0', &xfered_label);
00709 
00710     printf(" %ls%-*s ", wcfname, padwid, "");
00711     /* We will show 1.62M/s, 11.6M/s, but 116K/s and 1116K/s */
00712     if(rate_human < 9.995) {
00713         printf("%6.1f %3s  %4.2f%c/s ",
00714                 xfered_human, xfered_label, rate_human, rate_label[0]);
00715     } else if(rate_human < 99.95) {
00716         printf("%6.1f %3s  %4.1f%c/s ",
00717                 xfered_human, xfered_label, rate_human, rate_label[0]);
00718     } else {
00719         printf("%6.1f %3s  %4.f%c/s ",
00720                 xfered_human, xfered_label, rate_human, rate_label[0]);
00721     }
00722     if(eta_h == 0) {
00723         printf("%02u:%02u", eta_m, eta_s);
00724     } else if(eta_h < 100) {
00725         printf("%02u:%02u:%02u", eta_h, eta_m, eta_s);
00726     } else {
00727         fputs("--:--", stdout);
00728     }
00729 
00730     free(fname);
00731     free(wcfname);
00732 
00733     if(totaldownload) {
00734         fill_progress(file_percent, total_percent, cols - infolen);
00735     } else {
00736         fill_progress(file_percent, file_percent, cols - infolen);
00737     }
00738     return;
00739 }
00740 
00741 /* Callback to handle notifications from the library */
00742 void cb_log(alpm_loglevel_t level, const char *fmt, va_list args)
00743 {
00744     if(!fmt || strlen(fmt) == 0) {
00745         return;
00746     }
00747 
00748     if(on_progress) {
00749         char *string = NULL;
00750         pm_vasprintf(&string, level, fmt, args);
00751         if(string != NULL) {
00752             output = alpm_list_add(output, string);
00753         }
00754     } else {
00755         pm_vfprintf(stderr, level, fmt, args);
00756     }
00757 }
00758 
00759 /* vim: set ts=2 sw=2 noet: */