<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[117044] trunk/base/src</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="https://trac.macports.org/changeset/117044">117044</a></dd>
<dt>Author</dt> <dd>cal@macports.org</dd>
<dt>Date</dt> <dd>2014-02-13 13:46:10 -0800 (Thu, 13 Feb 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>base: display a progress indicator for slow downloads</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkbasesrcmacports10macportstcl">trunk/base/src/macports1.0/macports.tcl</a></li>
<li><a href="#trunkbasesrcpackage10portarchivefetchtcl">trunk/base/src/package1.0/portarchivefetch.tcl</a></li>
<li><a href="#trunkbasesrcpextlib10curlc">trunk/base/src/pextlib1.0/curl.c</a></li>
<li><a href="#trunkbasesrcportporttcl">trunk/base/src/port/port.tcl</a></li>
<li><a href="#trunkbasesrcport10portfetchtcl">trunk/base/src/port1.0/portfetch.tcl</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkbasesrcmacports10macportstcl"></a>
<div class="modfile"><h4>Modified: trunk/base/src/macports1.0/macports.tcl (117043 => 117044)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/base/src/macports1.0/macports.tcl        2014-02-13 21:28:21 UTC (rev 117043)
+++ trunk/base/src/macports1.0/macports.tcl        2014-02-13 21:46:10 UTC (rev 117044)
</span><span class="lines">@@ -1276,8 +1276,11 @@
</span><span class="cx">         foreach phase $macports::port_phases {
</span><span class="cx">             $workername alias ui_${priority}_$phase ui_${priority}_$phase
</span><span class="cx">         }
</span><del>-
</del><span class="cx">     }
</span><ins>+    # add the UI progress call-back
+    if {[info exists macports::ui_options(progress_download)]} {
+        $workername alias ui_progress_download $macports::ui_options(progress_download)
+    }
</ins><span class="cx"> 
</span><span class="cx">     $workername alias ui_prefix ui_prefix
</span><span class="cx">     $workername alias ui_channels ui_channels
</span><span class="lines">@@ -1435,7 +1438,7 @@
</span><span class="cx"> # @param local one, if the URL is local, zero otherwise
</span><span class="cx"> # @return a path to a directory containing the Portfile, or an error code
</span><span class="cx"> proc macports::fetch_port {url {local 0}} {
</span><del>-    global macports::portdbpath macports::ui_prefix macports::portverbose
</del><ins>+    global macports::portdbpath macports::ui_prefix macports::portverbose macports::ui_options
</ins><span class="cx"> 
</span><span class="cx">     set fetchdir [file join $portdbpath portdirs]
</span><span class="cx">     file mkdir $fetchdir
</span><span class="lines">@@ -1448,11 +1451,13 @@
</span><span class="cx">     } else {
</span><span class="cx">         ui_msg &quot;$macports::ui_prefix Fetching port $url&quot;
</span><span class="cx">         set fetchfile [file tail $url]
</span><del>-        set verboseflag {}
</del><ins>+        set progressflag {}
</ins><span class="cx">         if {$macports::portverbose eq {yes}} {
</span><del>-            set verboseflag -v
</del><ins>+            set progressflag &quot;--progress builtin&quot;
+        } elseif {[info exists macports::ui_options(progress_download)]} {
+            set progressflag &quot;--progress ${macports::ui_options(progress_download)}&quot;
</ins><span class="cx">         }
</span><del>-        if {[catch {eval curl fetch $verboseflag {$url} {[file join $fetchdir $fetchfile]}} result]} {
</del><ins>+        if {[catch {eval curl fetch $progressflag {$url} {[file join $fetchdir $fetchfile]}} result]} {
</ins><span class="cx">             return -code error &quot;Port remote fetch failed: $result&quot;
</span><span class="cx">         }
</span><span class="cx">     }
</span><span class="lines">@@ -2262,7 +2267,8 @@
</span><span class="cx"> proc mportsync {{optionslist {}}} {
</span><span class="cx">     global macports::sources macports::portdbpath macports::rsync_options \
</span><span class="cx">            tcl_platform macports::portverbose macports::autoconf::rsync_path \
</span><del>-           macports::autoconf::tar_path macports::autoconf::openssl_path
</del><ins>+           macports::autoconf::tar_path macports::autoconf::openssl_path \
+           macports::ui_options
</ins><span class="cx">     array set options $optionslist
</span><span class="cx">     if {[info exists options(no_reindex)]} {
</span><span class="cx">         upvar $options(needed_portindex_var) any_needed_portindex
</span><span class="lines">@@ -2505,12 +2511,14 @@
</span><span class="cx"> 
</span><span class="cx">                 file mkdir $destdir
</span><span class="cx"> 
</span><del>-                set verboseflag {}
</del><ins>+                set progressflag {}
</ins><span class="cx">                 if {$macports::portverbose eq {yes}} {
</span><del>-                    set verboseflag -v
</del><ins>+                    set progressflag &quot;--progress builtin&quot;
+                } elseif {[info exists macports::ui_options(progress_download)]} {
+                    set progressflag &quot;--progress ${macports::ui_options(progress_download)}&quot;
</ins><span class="cx">                 }
</span><span class="cx"> 
</span><del>-                if {[catch {eval curl fetch $verboseflag {$source} {$tarpath}} error]} {
</del><ins>+                if {[catch {eval curl fetch $progressflag {$source} {$tarpath}} error]} {
</ins><span class="cx">                     ui_error &quot;Fetching $source failed ($error)&quot;
</span><span class="cx">                     incr numfailed
</span><span class="cx">                     continue
</span></span></pre></div>
<a id="trunkbasesrcpackage10portarchivefetchtcl"></a>
<div class="modfile"><h4>Modified: trunk/base/src/package1.0/portarchivefetch.tcl (117043 => 117044)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/base/src/package1.0/portarchivefetch.tcl        2014-02-13 21:28:21 UTC (rev 117043)
+++ trunk/base/src/package1.0/portarchivefetch.tcl        2014-02-13 21:46:10 UTC (rev 117044)
</span><span class="lines">@@ -187,7 +187,11 @@
</span><span class="cx">         lappend fetch_options &quot;--ignore-ssl-cert&quot;
</span><span class="cx">     }
</span><span class="cx">     if {$portverbose eq &quot;yes&quot;} {
</span><del>-        lappend fetch_options &quot;-v&quot;
</del><ins>+        lappend fetch_options &quot;--progress&quot;
+        lappend fetch_options &quot;builtin&quot;
+    } elseif {[llength [info commands ui_progress_download]] &gt; 0} {
+        lappend fetch_options &quot;--progress&quot;
+        lappend fetch_options &quot;ui_progress_download&quot;
</ins><span class="cx">     }
</span><span class="cx">     set sorted no
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkbasesrcpextlib10curlc"></a>
<div class="modfile"><h4>Modified: trunk/base/src/pextlib1.0/curl.c (117043 => 117044)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/base/src/pextlib1.0/curl.c        2014-02-13 21:28:21 UTC (rev 117043)
+++ trunk/base/src/pextlib1.0/curl.c        2014-02-13 21:46:10 UTC (rev 117044)
</span><span class="lines">@@ -56,6 +56,7 @@
</span><span class="cx"> #define _CURL_CONNECTION_TIMEOUT        ((long)(30))                /* 30 seconds */
</span><span class="cx"> #define _CURL_MINIMUM_XFER_SPEED        ((long)1024)                /* 1KB/sec */
</span><span class="cx"> #define _CURL_MINIMUM_XFER_TIMEOUT        ((long)(60))                /* 1 minute */
</span><ins>+#define _CURL_MINIMUM_PROGRESS_INTERVAL ((double)(0.2)) /* 0.2 seconds */
</ins><span class="cx"> 
</span><span class="cx"> /* ========================================================================= **
</span><span class="cx">  * Definitions
</span><span class="lines">@@ -78,6 +79,15 @@
</span><span class="cx"> int CurlGetSizeCmd(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]);
</span><span class="cx"> int CurlPostCmd(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]);
</span><span class="cx"> 
</span><ins>+typedef struct {
+        Tcl_Interp *interp;
+        const char *proc;
+        double prevcalltime;
+} tcl_callback_t;
+
+static int CurlProgressHandler(tcl_callback_t *callback, double dltotal, double dlnow, double ultotal, double ulnow);
+static void CurlProgressCleanup(tcl_callback_t *callback);
+
</ins><span class="cx"> void CurlInit(void);
</span><span class="cx"> 
</span><span class="cx"> /* ========================================================================= **
</span><span class="lines">@@ -112,7 +122,7 @@
</span><span class="cx"> /**
</span><span class="cx">  * curl fetch subcommand entry point.
</span><span class="cx">  *
</span><del>- * syntax: curl fetch [-v] [--disable-epsv] [--ignore-ssl-cert] [--remote-time] [-u userpass] [--effective-url lasturlvar] url filename
</del><ins>+ * syntax: curl fetch [--disable-epsv] [--ignore-ssl-cert] [--remote-time] [-u userpass] [--effective-url lasturlvar] [--progress &quot;builtin&quot;|callback] url filename
</ins><span class="cx">  *
</span><span class="cx">  * @param interp                current interpreter
</span><span class="cx">  * @param objc                        number of parameters
</span><span class="lines">@@ -133,6 +143,11 @@
</span><span class="cx">                 int remotetime = 0;
</span><span class="cx">                 const char* theUserPassString = NULL;
</span><span class="cx">                 const char* effectiveURLVarName = NULL;
</span><ins>+                tcl_callback_t progressCallback = {
+                        .interp = interp,
+                        .proc = NULL,
+                        .prevcalltime = 0.0
+                };
</ins><span class="cx">                 char* effectiveURL = NULL;
</span><span class="cx">                 char* userAgent = PACKAGE_NAME &quot;/&quot; PACKAGE_VERSION &quot; libcurl/&quot; LIBCURL_VERSION;
</span><span class="cx">                 int optioncrsr;
</span><span class="lines">@@ -152,9 +167,7 @@
</span><span class="cx">                         /* get the option */
</span><span class="cx">                         const char* theOption = Tcl_GetString(objv[optioncrsr]);
</span><span class="cx"> 
</span><del>-                        if (strcmp(theOption, &quot;-v&quot;) == 0) {
-                                noprogress = 0;
-                        } else if (strcmp(theOption, &quot;--disable-epsv&quot;) == 0) {
</del><ins>+                        if (strcmp(theOption, &quot;--disable-epsv&quot;) == 0) {
</ins><span class="cx">                                 useepsv = 0;
</span><span class="cx">                         } else if (strcmp(theOption, &quot;--ignore-ssl-cert&quot;) == 0) {
</span><span class="cx">                                 ignoresslcert = 1;
</span><span class="lines">@@ -196,6 +209,19 @@
</span><span class="cx">                                         theResult = TCL_ERROR;
</span><span class="cx">                                         break;
</span><span class="cx">                                 }
</span><ins>+                        } else if (strcmp(theOption, &quot;--progress&quot;) == 0) {
+                                /* check we also have the parameter */
+                                if (optioncrsr &lt; lastoption) {
+                                        optioncrsr++;
+                                        noprogress = 0;
+                                        progressCallback.proc = Tcl_GetString(objv[optioncrsr]);
+                                } else {
+                                        Tcl_SetResult(interp,
+                                                &quot;curl fetch: --progress option requires a parameter&quot;,
+                                                TCL_STATIC);
+                                        theResult = TCL_ERROR;
+                                        break;
+                                }
</ins><span class="cx">                         } else {
</span><span class="cx">                                 Tcl_ResetResult(interp);
</span><span class="cx">                                 Tcl_AppendResult(interp, &quot;curl fetch: unknown option &quot;, theOption, NULL);
</span><span class="lines">@@ -340,6 +366,21 @@
</span><span class="cx">                         break;
</span><span class="cx">                 }
</span><span class="cx"> 
</span><ins>+                /* we want/don't want a custom progress function */
+                if (noprogress == 0 &amp;&amp; strcmp(progressCallback.proc, &quot;builtin&quot;) != 0) {
+                        theCurlCode = curl_easy_setopt(theHandle, CURLOPT_PROGRESSDATA, &amp;progressCallback);
+                        if (theCurlCode != CURLE_OK) {
+                                theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
+                                break;
+                        }
+
+                        theCurlCode = curl_easy_setopt(theHandle, CURLOPT_PROGRESSFUNCTION, CurlProgressHandler);
+                        if (theCurlCode != CURLE_OK) {
+                                theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
+                                break;
+                        }
+                }
+
</ins><span class="cx">                 /* we want/don't want to use epsv */
</span><span class="cx">                 theCurlCode = curl_easy_setopt(theHandle, CURLOPT_FTP_USE_EPSV, useepsv);
</span><span class="cx">                 if (theCurlCode != CURLE_OK) {
</span><span class="lines">@@ -398,6 +439,11 @@
</span><span class="cx">                         break;
</span><span class="cx">                 }
</span><span class="cx"> 
</span><ins>+                /* signal cleanup to the progress callback */
+                if (noprogress == 0 &amp;&amp; strcmp(progressCallback.proc, &quot;builtin&quot;) != 0) {
+                        CurlProgressCleanup(&amp;progressCallback);
+                }
+
</ins><span class="cx">                 /* close the file */
</span><span class="cx">                 (void) fclose(theFile);
</span><span class="cx">                 theFile = NULL;
</span><span class="lines">@@ -926,6 +972,8 @@
</span><span class="cx"> /**
</span><span class="cx">  * curl post postdata url
</span><span class="cx">  *
</span><ins>+ * syntax: curl post [--user-agent useragentstring] [--progress &quot;builtin&quot;|callback] postdata url
+ *
</ins><span class="cx">  * @param interp                current interpreter
</span><span class="cx">  * @param objc                        number of parameters
</span><span class="cx">  * @param objv                        parameters
</span><span class="lines">@@ -941,19 +989,80 @@
</span><span class="cx">                 const char* theURL;
</span><span class="cx">                 const char* thePostData;
</span><span class="cx">                 CURLcode theCurlCode;
</span><ins>+                int noprogress = 1;
+                tcl_callback_t progressCallback = {
+                        .interp = interp,
+                        .proc = NULL,
+                        .prevcalltime = 0.0
+                };
+                char* userAgent = PACKAGE_NAME &quot;/&quot; PACKAGE_VERSION &quot; libcurl/&quot; LIBCURL_VERSION;
+                int optioncrsr;
+                int lastoption;
</ins><span class="cx"> 
</span><del>-                /* check the number of parameters */
-                if (objc != 4) {
-                        Tcl_WrongNumArgs(interp, 1, objv, &quot;postdata url&quot;);
</del><ins>+                /* we might have options and then postdata and the url */
+                /* let's process the options first */
+
+                optioncrsr = 2;
+                lastoption = objc - 3;
+                while (optioncrsr &lt;= lastoption) {
+                        /* get the option */
+                        const char* theOption = Tcl_GetString(objv[optioncrsr]);
+
+                        if (strcmp(theOption, &quot;--user-agent&quot;) == 0) {
+                                /* check we also have the parameter */
+                                if (optioncrsr &lt; lastoption) {
+                                        optioncrsr++;
+                                        userAgent = Tcl_GetString(objv[optioncrsr]);
+                                } else {
+                                        Tcl_SetResult(interp,
+                                                &quot;curl post: --user-agent option requires a parameter&quot;,
+                                                TCL_STATIC);
+                                        theResult = TCL_ERROR;
+                                        break;
+                                }
+                        } else if (strcmp(theOption, &quot;--progress&quot;) == 0) {
+                                /* check we also have the parameter */
+                                if (optioncrsr &lt; lastoption) {
+                                        optioncrsr++;
+                                        noprogress = 0;
+                                        progressCallback.proc = Tcl_GetString(objv[optioncrsr]);
+                                } else {
+                                        Tcl_SetResult(interp,
+                                                &quot;curl post: --progress option requires a parameter&quot;,
+                                                TCL_STATIC);
+                                        theResult = TCL_ERROR;
+                                        break;
+                                }
+                        } else {
+                                Tcl_ResetResult(interp);
+                                Tcl_AppendResult(interp, &quot;curl post: unknown option &quot;, theOption, NULL);
+                                theResult = TCL_ERROR;
+                                break;
+                        }
+
+                        optioncrsr++;
+                }
+
+                if (optioncrsr &lt;= lastoption) {
+                        /* something went wrong */
+                        break;
+                }
+
+                /*        first (second) parameter is -v or the url,
+                        second (third) parameter is the file */
+
+                if (objc &gt;= 4) {
+                        /* Retrieve the url - it is the last parameter */
+                        theURL = Tcl_GetString(objv[objc - 1]);
+
+                        /* Retrieve the post data - it's before the url */
+                        thePostData = Tcl_GetString(objv[objc - 2]);
+                } else {
+                        Tcl_WrongNumArgs(interp, 1, objv, &quot;post [options] postdata file&quot;);
</ins><span class="cx">                         theResult = TCL_ERROR;
</span><span class="cx">                         break;
</span><span class="cx">                 }
</span><span class="cx"> 
</span><del>-                /* Retrieve the url - it is the last parameter */
-                theURL = Tcl_GetString(objv[objc - 1]);
-
-                /* Retrieve the post data - it's before the url */
-                thePostData = Tcl_GetString(objv[objc - 2]);
</del><span class="cx">                 /* Open the file (dev/null) */
</span><span class="cx">                 theFile = fopen(&quot;/dev/null&quot;, &quot;a&quot;);
</span><span class="cx">                 if (theFile == NULL) {
</span><span class="lines">@@ -1012,6 +1121,13 @@
</span><span class="cx">                         break;
</span><span class="cx">                 }
</span><span class="cx"> 
</span><ins>+                /* -A option */
+                theCurlCode = curl_easy_setopt(theHandle, CURLOPT_USERAGENT, userAgent);
+                if (theCurlCode != CURLE_OK) {
+                        theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
+                        break;
+                }
+
</ins><span class="cx">                 /* set timeout on connections */
</span><span class="cx">                 theCurlCode = curl_easy_setopt(theHandle, CURLOPT_CONNECTTIMEOUT, _CURL_CONNECTION_TIMEOUT);
</span><span class="cx">                 if (theCurlCode != CURLE_OK) {
</span><span class="lines">@@ -1047,13 +1163,28 @@
</span><span class="cx">                         break;
</span><span class="cx">                 }
</span><span class="cx"> 
</span><del>-                /* we do not want any progress */
-                theCurlCode = curl_easy_setopt(theHandle, CURLOPT_NOPROGRESS, 1);
</del><ins>+                /* we want/don't want progress */
+                theCurlCode = curl_easy_setopt(theHandle, CURLOPT_NOPROGRESS, noprogress);
</ins><span class="cx">                 if (theCurlCode != CURLE_OK) {
</span><span class="cx">                         theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
</span><span class="cx">                         break;
</span><span class="cx">                 }
</span><span class="cx"> 
</span><ins>+                /* we want/don't want a custom progress function */
+                if (noprogress == 0 &amp;&amp; strcmp(progressCallback.proc, &quot;builtin&quot;) != 0) {
+                        theCurlCode = curl_easy_setopt(theHandle, CURLOPT_PROGRESSDATA, &amp;progressCallback);
+                        if (theCurlCode != CURLE_OK) {
+                                theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
+                                break;
+                        }
+
+                        theCurlCode = curl_easy_setopt(theHandle, CURLOPT_PROGRESSFUNCTION, CurlProgressHandler);
+                        if (theCurlCode != CURLE_OK) {
+                                theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
+                                break;
+                        }
+                }
+
</ins><span class="cx">                 /* actually perform the POST */
</span><span class="cx">                 theCurlCode = curl_easy_perform(theHandle);
</span><span class="cx">                 if (theCurlCode != CURLE_OK) {
</span><span class="lines">@@ -1061,6 +1192,11 @@
</span><span class="cx">                         break;
</span><span class="cx">                 }
</span><span class="cx"> 
</span><ins>+                /* signal cleanup to the progress callback */
+                if (noprogress == 0 &amp;&amp; strcmp(progressCallback.proc, &quot;builtin&quot;) != 0) {
+                        CurlProgressCleanup(&amp;progressCallback);
+                }
+
</ins><span class="cx">                 /* close the file */
</span><span class="cx">                 (void) fclose(theFile);
</span><span class="cx">                 theFile = NULL;
</span><span class="lines">@@ -1151,3 +1287,141 @@
</span><span class="cx"> {
</span><span class="cx">         curl_global_init(CURL_GLOBAL_ALL);
</span><span class="cx"> }
</span><ins>+
+/* ========================================================================= **
+ * Callback function
+ * ========================================================================= */
+#pragma mark -
+#pragma mark Callback function
+static int CurlProgressHandler(
+                tcl_callback_t *callback,
+                double dltotal,
+                double dlnow,
+                double ultotal,
+                double ulnow)
+{
+        if (dltotal == 0.0 &amp;&amp; ultotal == 0.0 &amp;&amp; dlnow == 0.0 &amp;&amp; ulnow == 0.0) {
+                /*
+                 * We have no idea whether this is an up- or download. Do nothing for now.
+                 */
+                return 0;
+        }
+
+        enum {
+                UPLOAD,
+                DOWNLOAD
+        } transferType;
+
+        double total, now, speed, curtime;
+
+        if (dltotal != 0.0 || dlnow != 0.0) {
+                /* This is a download */
+                transferType = DOWNLOAD;
+                total = dltotal;
+                now = dlnow;
+        } else {
+                /* This is an upload */
+                transferType = UPLOAD;
+                total = ultotal;
+                now = ulnow;
+        }
+
+        /* Only send updates once a second */
+        curl_easy_getinfo(theHandle, CURLINFO_TOTAL_TIME, &amp;curtime);
+        if ((curtime - callback-&gt;prevcalltime) &lt; _CURL_MINIMUM_PROGRESS_INTERVAL) {
+                return 0;
+        }
+
+        if (callback-&gt;prevcalltime == 0.0) {
+                /* this is the first time we're calling the callback, call start
+                 * subcommand first */
+
+                /*
+                 * Command string, a space followed &quot;start&quot;, another space and &quot;dl&quot; or
+                 * &quot;ul&quot; plus the trailing \0.
+                 */
+                char startCommandBuffer[strlen(callback-&gt;proc) + (1 + 5) + (1 + 2) + 1];
+                int startLen = 0;
+
+                startLen = snprintf(startCommandBuffer, sizeof(startCommandBuffer), &quot;%s start %s&quot;,
+                                callback-&gt;proc, (transferType == DOWNLOAD) ? &quot;dl&quot; : &quot;ul&quot;);
+                if (startLen &lt; 0 || (size_t) startLen &gt;= sizeof(startCommandBuffer)) {
+                        /* overflow */
+                        fprintf(stderr, &quot;pextlib1.0: buffer overflow in &quot; __FILE__ &quot;:%d. Buffer is: %s\n&quot;, __LINE__, startCommandBuffer);
+                        abort();
+                }
+
+                if (TCL_ERROR == Tcl_EvalEx(callback-&gt;interp, startCommandBuffer, startLen, TCL_EVAL_GLOBAL)) {
+                        fprintf(stderr, &quot;curl progress callback failed: %s\n&quot;, Tcl_GetStringResult(callback-&gt;interp));
+                        return 1;
+                }
+        }
+
+        callback-&gt;prevcalltime = curtime;
+
+        /* Get the average speed from curl */
+        if (transferType == DOWNLOAD) {
+                curl_easy_getinfo(theHandle, CURLINFO_SPEED_DOWNLOAD, &amp;speed);
+        } else {
+                curl_easy_getinfo(theHandle, CURLINFO_SPEED_UPLOAD, &amp;speed);
+        }
+
+        /*
+         * We need the command string, a space and &quot;update&quot;, another space and &quot;dl&quot;
+         * or &quot;ul&quot;, three doubles converted to string (see comment below), plus
+         * a space character for separation per argument, so 3 * (1 + LEN_DOUBLE)
+         * plus one character for the null-byte.
+         */
+        char commandBuffer[strlen(callback-&gt;proc) + (1 + 6) + (1 + 2) + 3 * (1 + 12) + 1];
+        int len = 0;
+
+        /*
+         * Format numbers using % .6g format specifier so we can always be sure
+         * what the total length will be: .6g tells us we're using at most
+         * 6 significant digits; that means 6 characters, another one for
+         * a possible decimal point, another 4 for e+XX where 00 &lt;= XX &lt;= 99 for
+         * exponents, and another one for a possible sign (or &quot; &quot; for positive
+         * numbers). In total, the maximum length will be 12 per double formatted.
+         */
+        len = snprintf(commandBuffer, sizeof(commandBuffer), &quot;%s update %s % .6g % .6g % .6g&quot;,
+                        callback-&gt;proc, (transferType == DOWNLOAD) ? &quot;dl&quot; : &quot;ul&quot;, total, now, speed);
+        if (len &lt; 0 || (size_t) len &gt;= sizeof(commandBuffer)) {
+                /* overflow */
+                fprintf(stderr, &quot;pextlib1.0: buffer overflow in &quot; __FILE__ &quot;:%d. Buffer is: %s\n&quot;, __LINE__, commandBuffer);
+                abort();
+        }
+
+        /*
+         * Execute directly rather than compiling to bytecode first - the script is
+         * likely to change in the next call anyway.
+         */
+        if (TCL_ERROR == Tcl_EvalEx(callback-&gt;interp, commandBuffer, len, TCL_EVAL_GLOBAL)) {
+                fprintf(stderr, &quot;curl progress callback failed: %s\n&quot;, Tcl_GetStringResult(callback-&gt;interp));
+                return 1;
+        }
+
+        return 0;
+}
+
+static void CurlProgressCleanup(
+                tcl_callback_t *callback)
+{
+        /*
+         * Transfer complete, signal the progress callback
+         */
+
+        /*
+         * Command string, a space followed &quot;finish&quot; plus the trailing \0.
+         */
+        char commandBuffer[strlen(callback-&gt;proc) + (1 + 6) + 1];
+        int len = 0;
+
+        len = snprintf(commandBuffer, sizeof(commandBuffer), &quot;%s finish&quot;, callback-&gt;proc);
+        if (len &lt; 0 || (size_t) len &gt;= sizeof(commandBuffer)) {
+                /* overflow */
+                fprintf(stderr, &quot;pextlib1.0: buffer overflow in &quot; __FILE__ &quot;:%d. Buffer is: %s\n&quot;, __LINE__, commandBuffer);
+                abort();
+        }
+
+        Tcl_EvalEx(callback-&gt;interp, commandBuffer, len, TCL_EVAL_GLOBAL);
+}
</ins></span></pre></div>
<a id="trunkbasesrcportporttcl"></a>
<div class="modfile"><h4>Modified: trunk/base/src/port/port.tcl (117043 => 117044)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/base/src/port/port.tcl        2014-02-13 21:28:21 UTC (rev 117043)
+++ trunk/base/src/port/port.tcl        2014-02-13 21:46:10 UTC (rev 117044)
</span><span class="lines">@@ -34,6 +34,8 @@
</span><span class="cx"> # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
</span><span class="cx"> # POSSIBILITY OF SUCH DAMAGE.
</span><span class="cx"> 
</span><ins>+package require term::ansi::send
+
</ins><span class="cx"> source [file join &quot;@macports_tcl_dir@&quot; macports1.0 macports_fastload.tcl]
</span><span class="cx"> package require macports
</span><span class="cx"> package require Pextlib 1.0
</span><span class="lines">@@ -155,7 +157,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> # Format an integer representing bytes using given units
</span><del>-proc bytesize {siz {unit {}}} {
</del><ins>+proc bytesize {siz {unit {}} {format {%.3f}}} {
</ins><span class="cx">     if {$unit == {}} {
</span><span class="cx">         if {$siz &gt; 0x40000000} {
</span><span class="cx">             set unit &quot;GiB&quot;
</span><span class="lines">@@ -193,7 +195,7 @@
</span><span class="cx">         }
</span><span class="cx">     }
</span><span class="cx">     if {[expr {round($siz)}] != $siz} {
</span><del>-        set siz [format {%.3f} $siz]
</del><ins>+        set siz [format $format $siz]
</ins><span class="cx">     }
</span><span class="cx">     return &quot;$siz $unit&quot;
</span><span class="cx"> }
</span><span class="lines">@@ -4787,7 +4789,170 @@
</span><span class="cx">     return $exit_status
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+##
+# Progress callback for downloads executed by macports 1.0.
+#
+# This is essentially a cURL progress callback.
+#
+# @param action
+#        One of &quot;start&quot;, &quot;update&quot; or &quot;finish&quot;, where start will be called
+#        before any number of change calls, followed by one call to finish.
+# @param args
+#        A list of variadic args that differ for each action.
+#        For &quot;start&quot;: contains a single argument &quot;ul&quot; or &quot;dl&quot; indicating
+#        whether this is an up- or download.
+#        For &quot;update&quot;: contains the arguments (&quot;ul&quot;|&quot;dl&quot;) total now speed where
+#        ul/dl are as for start, and total, now and speed are doubles
+#        indicating the total transfer size, currently transferred amount and
+#        average speed per second in bytes.
+#        For &quot;finish&quot;: empty.
+proc port_progress_download {action args} {
+    global _port_progress_starttime _port_progress_display_bar
+    switch -nocase -- $action {
+        start {
+            set _port_progress_starttime [clock milliseconds]
+            set _port_progress_display_bar no
+        }
+        update {
+            # the for loop is a simple hack because Tcl 8.4 doesn't have
+            # lassign
+            foreach {type total now speed} $args {
+                if {${_port_progress_display_bar} ne yes} {
+                    # check whether we should show a progress bar for this transfer
+                    if {[expr {[clock milliseconds] - ${_port_progress_starttime}}] &gt; 500 &amp;&amp; ($total == 0 || [expr {$now / $total}] &lt; 0.5)} {
+                        # wait 500ms, then, if we don't know the total or we're
+                        # not past 50% yet, display a progress bar.
+                        set _port_progress_display_bar yes
+                    }
+                }
+                if {${_port_progress_display_bar} eq yes} {
+                    set barprefix &quot;     &quot;
+                    if {$total != 0} {
+                        set barsuffix [format &quot;        speed: %-13s&quot; &quot;[bytesize $speed {} &quot;%.1f&quot;]/s&quot;]
+                        progress_bar $now $total 20 $barprefix $barsuffix
+                    } else {
+                        set barsuffix [format &quot; %-10s     speed: %-13s&quot; [bytesize $now {} &quot;%6.1f&quot;] &quot;[bytesize $speed {} &quot;%.1f&quot;]/s&quot;]
+                        unprogress_bar $now 20 $barprefix $barsuffix
+                    }
+                }
+            }
+        }
+        finish {
+            # erase to start of line
+            ::term::ansi::send::esol
+            # return cursor to start of line
+            puts -nonewline &quot;\r&quot;
+            flush stdout
+        }
+    }
</ins><span class="cx"> 
</span><ins>+    return 0
+}
+
+##
+# Draw a progress bar using unicode block drawing characters
+#
+# @param current
+#        the current progress value
+# @param total
+#        the progress value representing 100%
+# @param halfwidth
+#        the half width in characters of the progress bar
+# @param prefix
+#        prefix to be printed in front of the progress bar
+# @param suffix
+#        suffix to be printed after the progress bar
+proc progress_bar {current total halfwidth {prefix &quot;&quot;} {suffix &quot;&quot;}} {
+    # we use 8 different states per character, so let's multiply the width by
+    # 8 and map the percentage to this range
+    set percent [expr {($current * 100 / $total)}]
+    set progress [expr {int(round(($current * $halfwidth * 8) / $total))}]
+    set fullfields [expr {int($progress / 8)}]
+    set remainder [expr {$progress % 8}]
+
+    # clear the current line
+    set progressbar &quot;&quot;
+    for {set i 0} {$i &lt; $fullfields} {incr i} {
+        # U+2588 FULL BLOCK doesn't match the other blocks in some fonts :/
+        # Use two half blocks instead
+        # Since we use two chars here, make sure to remove a space for each of
+        # those used!
+        append progressbar &quot;\u258c\u258c&quot;
+    }
+
+    if {$remainder == 0 &amp;&amp; $fullfields &lt; $halfwidth} {
+        append progressbar &quot; &quot;
+    } elseif {$remainder == 1} {
+        # U+258F LEFT ONE EIGHTH BLOCK
+        append progressbar &quot;\u258f&quot;
+    } elseif {$remainder == 2} {
+        # U+258E LEFT ONE QUARTER BLOCK
+        append progressbar &quot;\u258e&quot;
+    } elseif {$remainder == 3} {
+        # U+258D LEFT THREE EIGHTHS BLOCK
+        append progressbar &quot;\u258d&quot;
+    } elseif {$remainder == 4} {
+        # U+258C LEFT HALF BLOCK
+        append progressbar &quot;\u258c&quot;
+    } elseif {$remainder == 5} {
+        # U+258B LEFT FIVE EIGHTHS BLOCK
+        append progressbar &quot;\u258b&quot;
+    } elseif {$remainder == 6} {
+        # U+258A LEFT THREE QUARTERS BLOCK
+        append progressbar &quot;\u258a&quot;
+    } elseif {$remainder == 7} {
+        # U+2589 LEFT SEVEN EIGHTHS BLOCK
+        append progressbar &quot;\u2589&quot;
+    }
+
+    for {set i [expr {[string length $progressbar]}]} {$i &lt; [expr {2 * $halfwidth}]} {incr i} {
+        append progressbar &quot; &quot;
+    }
+    set percentagesuffix [format &quot; %5.1f %%&quot; $percent]
+
+    puts -nonewline &quot;\r${prefix}\[${progressbar}\]${percentagesuffix}${suffix}&quot;
+    flush stdout
+}
+
+##
+# Draw a progress indicator
+#
+# @param current
+#        the number of bytes currently downloaded
+# @param halfwidth
+#        the half width in characters of the progress indicator
+# @param prefix
+#        prefix to be printed in front of the progress indicator
+# @param suffix
+#        suffix to be printed after the progress indicator
+proc unprogress_bar {current halfwidth {prefix &quot;&quot;} {suffix &quot;&quot;}} {
+    global _port_progress_unprogressbar_state
+
+    set numstates 4
+
+    if {![info exists _port_progress_unprogressbar_state]} {
+        set _port_progress_unprogressbar_state 0
+    } else {
+        set _port_progress_unprogressbar_state [expr {(${_port_progress_unprogressbar_state} + 1) % $numstates}]
+    }
+
+    # clear the current line
+    set progressbar &quot;&quot;
+
+    for {set i 0} {$i &lt; [expr {2 * $halfwidth}]} {incr i} {
+        if {[expr $i % $numstates] == ${_port_progress_unprogressbar_state}} {
+            # U+2022 BULLET
+            append progressbar &quot;\u2022&quot;
+        } else {
+            append progressbar &quot; &quot;
+        }
+    }
+
+    puts -nonewline &quot;\r${prefix}\[${progressbar}\]${suffix}&quot;
+    flush stdout
+}
+
+
</ins><span class="cx"> ##########################################
</span><span class="cx"> # Main
</span><span class="cx"> ##########################################
</span><span class="lines">@@ -4835,6 +5000,10 @@
</span><span class="cx">     exit 1
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+if {[isatty stdout] &amp;&amp; (![info exists ui_options(ports_quiet)] || $ui_options(ports_quiet) ne &quot;yes&quot;)} {
+    set ui_options(progress_download) port_progress_download
+}
+
</ins><span class="cx"> # Get arguments remaining after option processing
</span><span class="cx"> set remaining_args [lrange $cmd_argv $cmd_argn end]
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkbasesrcport10portfetchtcl"></a>
<div class="modfile"><h4>Modified: trunk/base/src/port1.0/portfetch.tcl (117043 => 117044)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/base/src/port1.0/portfetch.tcl        2014-02-13 21:28:21 UTC (rev 117043)
+++ trunk/base/src/port1.0/portfetch.tcl        2014-02-13 21:46:10 UTC (rev 117044)
</span><span class="lines">@@ -502,7 +502,11 @@
</span><span class="cx">         lappend fetch_options &quot;--remote-time&quot;
</span><span class="cx">     }
</span><span class="cx">     if {$portverbose eq &quot;yes&quot;} {
</span><del>-        lappend fetch_options &quot;-v&quot;
</del><ins>+        lappend fetch_options &quot;--progress&quot;
+        lappend fetch_options &quot;builtin&quot;
+    } elseif {[llength [info commands ui_progress_download]] &gt; 0} {
+        lappend fetch_options &quot;--progress&quot;
+        lappend fetch_options &quot;ui_progress_download&quot;
</ins><span class="cx">     }
</span><span class="cx">     set sorted no
</span><span class="cx"> 
</span></span></pre>
</div>
</div>

</body>
</html>