[117044] trunk/base/src

cal at macports.org cal at macports.org
Thu Feb 13 13:46:10 PST 2014


Revision: 117044
          https://trac.macports.org/changeset/117044
Author:   cal at macports.org
Date:     2014-02-13 13:46:10 -0800 (Thu, 13 Feb 2014)
Log Message:
-----------
base: display a progress indicator for slow downloads

Modified Paths:
--------------
    trunk/base/src/macports1.0/macports.tcl
    trunk/base/src/package1.0/portarchivefetch.tcl
    trunk/base/src/pextlib1.0/curl.c
    trunk/base/src/port/port.tcl
    trunk/base/src/port1.0/portfetch.tcl

Modified: trunk/base/src/macports1.0/macports.tcl
===================================================================
--- 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)
@@ -1276,8 +1276,11 @@
         foreach phase $macports::port_phases {
             $workername alias ui_${priority}_$phase ui_${priority}_$phase
         }
-
     }
+    # add the UI progress call-back
+    if {[info exists macports::ui_options(progress_download)]} {
+        $workername alias ui_progress_download $macports::ui_options(progress_download)
+    }
 
     $workername alias ui_prefix ui_prefix
     $workername alias ui_channels ui_channels
@@ -1435,7 +1438,7 @@
 # @param local one, if the URL is local, zero otherwise
 # @return a path to a directory containing the Portfile, or an error code
 proc macports::fetch_port {url {local 0}} {
-    global macports::portdbpath macports::ui_prefix macports::portverbose
+    global macports::portdbpath macports::ui_prefix macports::portverbose macports::ui_options
 
     set fetchdir [file join $portdbpath portdirs]
     file mkdir $fetchdir
@@ -1448,11 +1451,13 @@
     } else {
         ui_msg "$macports::ui_prefix Fetching port $url"
         set fetchfile [file tail $url]
-        set verboseflag {}
+        set progressflag {}
         if {$macports::portverbose eq {yes}} {
-            set verboseflag -v
+            set progressflag "--progress builtin"
+        } elseif {[info exists macports::ui_options(progress_download)]} {
+            set progressflag "--progress ${macports::ui_options(progress_download)}"
         }
-        if {[catch {eval curl fetch $verboseflag {$url} {[file join $fetchdir $fetchfile]}} result]} {
+        if {[catch {eval curl fetch $progressflag {$url} {[file join $fetchdir $fetchfile]}} result]} {
             return -code error "Port remote fetch failed: $result"
         }
     }
@@ -2262,7 +2267,8 @@
 proc mportsync {{optionslist {}}} {
     global macports::sources macports::portdbpath macports::rsync_options \
            tcl_platform macports::portverbose macports::autoconf::rsync_path \
-           macports::autoconf::tar_path macports::autoconf::openssl_path
+           macports::autoconf::tar_path macports::autoconf::openssl_path \
+           macports::ui_options
     array set options $optionslist
     if {[info exists options(no_reindex)]} {
         upvar $options(needed_portindex_var) any_needed_portindex
@@ -2505,12 +2511,14 @@
 
                 file mkdir $destdir
 
-                set verboseflag {}
+                set progressflag {}
                 if {$macports::portverbose eq {yes}} {
-                    set verboseflag -v
+                    set progressflag "--progress builtin"
+                } elseif {[info exists macports::ui_options(progress_download)]} {
+                    set progressflag "--progress ${macports::ui_options(progress_download)}"
                 }
 
-                if {[catch {eval curl fetch $verboseflag {$source} {$tarpath}} error]} {
+                if {[catch {eval curl fetch $progressflag {$source} {$tarpath}} error]} {
                     ui_error "Fetching $source failed ($error)"
                     incr numfailed
                     continue

Modified: trunk/base/src/package1.0/portarchivefetch.tcl
===================================================================
--- 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)
@@ -187,7 +187,11 @@
         lappend fetch_options "--ignore-ssl-cert"
     }
     if {$portverbose eq "yes"} {
-        lappend fetch_options "-v"
+        lappend fetch_options "--progress"
+        lappend fetch_options "builtin"
+    } elseif {[llength [info commands ui_progress_download]] > 0} {
+        lappend fetch_options "--progress"
+        lappend fetch_options "ui_progress_download"
     }
     set sorted no
 

Modified: trunk/base/src/pextlib1.0/curl.c
===================================================================
--- 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)
@@ -56,6 +56,7 @@
 #define _CURL_CONNECTION_TIMEOUT	((long)(30))		/* 30 seconds */
 #define _CURL_MINIMUM_XFER_SPEED	((long)1024)		/* 1KB/sec */
 #define _CURL_MINIMUM_XFER_TIMEOUT	((long)(60))		/* 1 minute */
+#define _CURL_MINIMUM_PROGRESS_INTERVAL ((double)(0.2)) /* 0.2 seconds */
 
 /* ========================================================================= **
  * Definitions
@@ -78,6 +79,15 @@
 int CurlGetSizeCmd(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]);
 int CurlPostCmd(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]);
 
+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);
+
 void CurlInit(void);
 
 /* ========================================================================= **
@@ -112,7 +122,7 @@
 /**
  * curl fetch subcommand entry point.
  *
- * syntax: curl fetch [-v] [--disable-epsv] [--ignore-ssl-cert] [--remote-time] [-u userpass] [--effective-url lasturlvar] url filename
+ * syntax: curl fetch [--disable-epsv] [--ignore-ssl-cert] [--remote-time] [-u userpass] [--effective-url lasturlvar] [--progress "builtin"|callback] url filename
  *
  * @param interp		current interpreter
  * @param objc			number of parameters
@@ -133,6 +143,11 @@
 		int remotetime = 0;
 		const char* theUserPassString = NULL;
 		const char* effectiveURLVarName = NULL;
+		tcl_callback_t progressCallback = {
+			.interp = interp,
+			.proc = NULL,
+			.prevcalltime = 0.0
+		};
 		char* effectiveURL = NULL;
 		char* userAgent = PACKAGE_NAME "/" PACKAGE_VERSION " libcurl/" LIBCURL_VERSION;
 		int optioncrsr;
@@ -152,9 +167,7 @@
 			/* get the option */
 			const char* theOption = Tcl_GetString(objv[optioncrsr]);
 
-			if (strcmp(theOption, "-v") == 0) {
-				noprogress = 0;
-			} else if (strcmp(theOption, "--disable-epsv") == 0) {
+			if (strcmp(theOption, "--disable-epsv") == 0) {
 				useepsv = 0;
 			} else if (strcmp(theOption, "--ignore-ssl-cert") == 0) {
 				ignoresslcert = 1;
@@ -196,6 +209,19 @@
 					theResult = TCL_ERROR;
 					break;
 				}
+			} else if (strcmp(theOption, "--progress") == 0) {
+				/* check we also have the parameter */
+				if (optioncrsr < lastoption) {
+					optioncrsr++;
+					noprogress = 0;
+					progressCallback.proc = Tcl_GetString(objv[optioncrsr]);
+				} else {
+					Tcl_SetResult(interp,
+						"curl fetch: --progress option requires a parameter",
+						TCL_STATIC);
+					theResult = TCL_ERROR;
+					break;
+				}
 			} else {
 				Tcl_ResetResult(interp);
 				Tcl_AppendResult(interp, "curl fetch: unknown option ", theOption, NULL);
@@ -340,6 +366,21 @@
 			break;
 		}
 
+		/* we want/don't want a custom progress function */
+		if (noprogress == 0 && strcmp(progressCallback.proc, "builtin") != 0) {
+			theCurlCode = curl_easy_setopt(theHandle, CURLOPT_PROGRESSDATA, &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;
+			}
+		}
+
 		/* we want/don't want to use epsv */
 		theCurlCode = curl_easy_setopt(theHandle, CURLOPT_FTP_USE_EPSV, useepsv);
 		if (theCurlCode != CURLE_OK) {
@@ -398,6 +439,11 @@
 			break;
 		}
 
+		/* signal cleanup to the progress callback */
+		if (noprogress == 0 && strcmp(progressCallback.proc, "builtin") != 0) {
+			CurlProgressCleanup(&progressCallback);
+		}
+
 		/* close the file */
 		(void) fclose(theFile);
 		theFile = NULL;
@@ -926,6 +972,8 @@
 /**
  * curl post postdata url
  *
+ * syntax: curl post [--user-agent useragentstring] [--progress "builtin"|callback] postdata url
+ *
  * @param interp		current interpreter
  * @param objc			number of parameters
  * @param objv			parameters
@@ -941,19 +989,80 @@
 		const char* theURL;
 		const char* thePostData;
 		CURLcode theCurlCode;
+		int noprogress = 1;
+		tcl_callback_t progressCallback = {
+			.interp = interp,
+			.proc = NULL,
+			.prevcalltime = 0.0
+		};
+		char* userAgent = PACKAGE_NAME "/" PACKAGE_VERSION " libcurl/" LIBCURL_VERSION;
+		int optioncrsr;
+		int lastoption;
 
-		/* check the number of parameters */
-		if (objc != 4) {
-			Tcl_WrongNumArgs(interp, 1, objv, "postdata url");
+		/* we might have options and then postdata and the url */
+		/* let's process the options first */
+
+		optioncrsr = 2;
+		lastoption = objc - 3;
+		while (optioncrsr <= lastoption) {
+			/* get the option */
+			const char* theOption = Tcl_GetString(objv[optioncrsr]);
+
+			if (strcmp(theOption, "--user-agent") == 0) {
+				/* check we also have the parameter */
+				if (optioncrsr < lastoption) {
+					optioncrsr++;
+					userAgent = Tcl_GetString(objv[optioncrsr]);
+				} else {
+					Tcl_SetResult(interp,
+						"curl post: --user-agent option requires a parameter",
+						TCL_STATIC);
+					theResult = TCL_ERROR;
+					break;
+				}
+			} else if (strcmp(theOption, "--progress") == 0) {
+				/* check we also have the parameter */
+				if (optioncrsr < lastoption) {
+					optioncrsr++;
+					noprogress = 0;
+					progressCallback.proc = Tcl_GetString(objv[optioncrsr]);
+				} else {
+					Tcl_SetResult(interp,
+						"curl post: --progress option requires a parameter",
+						TCL_STATIC);
+					theResult = TCL_ERROR;
+					break;
+				}
+			} else {
+				Tcl_ResetResult(interp);
+				Tcl_AppendResult(interp, "curl post: unknown option ", theOption, NULL);
+				theResult = TCL_ERROR;
+				break;
+			}
+
+			optioncrsr++;
+		}
+
+		if (optioncrsr <= lastoption) {
+			/* something went wrong */
+			break;
+		}
+
+		/*	first (second) parameter is -v or the url,
+			second (third) parameter is the file */
+
+		if (objc >= 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, "post [options] postdata file");
 			theResult = TCL_ERROR;
 			break;
 		}
 
-		/* 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]);
 		/* Open the file (dev/null) */
 		theFile = fopen("/dev/null", "a");
 		if (theFile == NULL) {
@@ -1012,6 +1121,13 @@
 			break;
 		}
 
+		/* -A option */
+		theCurlCode = curl_easy_setopt(theHandle, CURLOPT_USERAGENT, userAgent);
+		if (theCurlCode != CURLE_OK) {
+			theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
+			break;
+		}
+
 		/* set timeout on connections */
 		theCurlCode = curl_easy_setopt(theHandle, CURLOPT_CONNECTTIMEOUT, _CURL_CONNECTION_TIMEOUT);
 		if (theCurlCode != CURLE_OK) {
@@ -1047,13 +1163,28 @@
 			break;
 		}
 
-		/* we do not want any progress */
-		theCurlCode = curl_easy_setopt(theHandle, CURLOPT_NOPROGRESS, 1);
+		/* we want/don't want progress */
+		theCurlCode = curl_easy_setopt(theHandle, CURLOPT_NOPROGRESS, noprogress);
 		if (theCurlCode != CURLE_OK) {
 			theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
 			break;
 		}
 
+		/* we want/don't want a custom progress function */
+		if (noprogress == 0 && strcmp(progressCallback.proc, "builtin") != 0) {
+			theCurlCode = curl_easy_setopt(theHandle, CURLOPT_PROGRESSDATA, &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;
+			}
+		}
+
 		/* actually perform the POST */
 		theCurlCode = curl_easy_perform(theHandle);
 		if (theCurlCode != CURLE_OK) {
@@ -1061,6 +1192,11 @@
 			break;
 		}
 
+		/* signal cleanup to the progress callback */
+		if (noprogress == 0 && strcmp(progressCallback.proc, "builtin") != 0) {
+			CurlProgressCleanup(&progressCallback);
+		}
+
 		/* close the file */
 		(void) fclose(theFile);
 		theFile = NULL;
@@ -1151,3 +1287,141 @@
 {
 	curl_global_init(CURL_GLOBAL_ALL);
 }
+
+/* ========================================================================= **
+ * 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 && ultotal == 0.0 && dlnow == 0.0 && 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, &curtime);
+	if ((curtime - callback->prevcalltime) < _CURL_MINIMUM_PROGRESS_INTERVAL) {
+		return 0;
+	}
+
+	if (callback->prevcalltime == 0.0) {
+		/* this is the first time we're calling the callback, call start
+		 * subcommand first */
+
+		/*
+		 * Command string, a space followed "start", another space and "dl" or
+		 * "ul" plus the trailing \0.
+		 */
+		char startCommandBuffer[strlen(callback->proc) + (1 + 5) + (1 + 2) + 1];
+		int startLen = 0;
+
+		startLen = snprintf(startCommandBuffer, sizeof(startCommandBuffer), "%s start %s",
+				callback->proc, (transferType == DOWNLOAD) ? "dl" : "ul");
+		if (startLen < 0 || (size_t) startLen >= sizeof(startCommandBuffer)) {
+			/* overflow */
+			fprintf(stderr, "pextlib1.0: buffer overflow in " __FILE__ ":%d. Buffer is: %s\n", __LINE__, startCommandBuffer);
+			abort();
+		}
+
+		if (TCL_ERROR == Tcl_EvalEx(callback->interp, startCommandBuffer, startLen, TCL_EVAL_GLOBAL)) {
+			fprintf(stderr, "curl progress callback failed: %s\n", Tcl_GetStringResult(callback->interp));
+			return 1;
+		}
+	}
+
+	callback->prevcalltime = curtime;
+
+	/* Get the average speed from curl */
+	if (transferType == DOWNLOAD) {
+		curl_easy_getinfo(theHandle, CURLINFO_SPEED_DOWNLOAD, &speed);
+	} else {
+		curl_easy_getinfo(theHandle, CURLINFO_SPEED_UPLOAD, &speed);
+	}
+
+	/*
+	 * We need the command string, a space and "update", another space and "dl"
+	 * or "ul", 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->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 <= XX <= 99 for
+	 * exponents, and another one for a possible sign (or " " for positive
+	 * numbers). In total, the maximum length will be 12 per double formatted.
+	 */
+	len = snprintf(commandBuffer, sizeof(commandBuffer), "%s update %s % .6g % .6g % .6g",
+			callback->proc, (transferType == DOWNLOAD) ? "dl" : "ul", total, now, speed);
+	if (len < 0 || (size_t) len >= sizeof(commandBuffer)) {
+		/* overflow */
+		fprintf(stderr, "pextlib1.0: buffer overflow in " __FILE__ ":%d. Buffer is: %s\n", __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->interp, commandBuffer, len, TCL_EVAL_GLOBAL)) {
+		fprintf(stderr, "curl progress callback failed: %s\n", Tcl_GetStringResult(callback->interp));
+		return 1;
+	}
+
+	return 0;
+}
+
+static void CurlProgressCleanup(
+		tcl_callback_t *callback)
+{
+	/*
+	 * Transfer complete, signal the progress callback
+	 */
+
+	/*
+	 * Command string, a space followed "finish" plus the trailing \0.
+	 */
+	char commandBuffer[strlen(callback->proc) + (1 + 6) + 1];
+	int len = 0;
+
+	len = snprintf(commandBuffer, sizeof(commandBuffer), "%s finish", callback->proc);
+	if (len < 0 || (size_t) len >= sizeof(commandBuffer)) {
+		/* overflow */
+		fprintf(stderr, "pextlib1.0: buffer overflow in " __FILE__ ":%d. Buffer is: %s\n", __LINE__, commandBuffer);
+		abort();
+	}
+
+	Tcl_EvalEx(callback->interp, commandBuffer, len, TCL_EVAL_GLOBAL);
+}

Modified: trunk/base/src/port/port.tcl
===================================================================
--- 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)
@@ -34,6 +34,8 @@
 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 # POSSIBILITY OF SUCH DAMAGE.
 
+package require term::ansi::send
+
 source [file join "@macports_tcl_dir@" macports1.0 macports_fastload.tcl]
 package require macports
 package require Pextlib 1.0
@@ -155,7 +157,7 @@
 
 
 # Format an integer representing bytes using given units
-proc bytesize {siz {unit {}}} {
+proc bytesize {siz {unit {}} {format {%.3f}}} {
     if {$unit == {}} {
         if {$siz > 0x40000000} {
             set unit "GiB"
@@ -193,7 +195,7 @@
         }
     }
     if {[expr {round($siz)}] != $siz} {
-        set siz [format {%.3f} $siz]
+        set siz [format $format $siz]
     }
     return "$siz $unit"
 }
@@ -4787,7 +4789,170 @@
     return $exit_status
 }
 
+##
+# Progress callback for downloads executed by macports 1.0.
+#
+# This is essentially a cURL progress callback.
+#
+# @param action
+#        One of "start", "update" or "finish", 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 "start": contains a single argument "ul" or "dl" indicating
+#        whether this is an up- or download.
+#        For "update": contains the arguments ("ul"|"dl") 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 "finish": 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}}] > 500 && ($total == 0 || [expr {$now / $total}] < 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 "     "
+                    if {$total != 0} {
+                        set barsuffix [format "        speed: %-13s" "[bytesize $speed {} "%.1f"]/s"]
+                        progress_bar $now $total 20 $barprefix $barsuffix
+                    } else {
+                        set barsuffix [format " %-10s     speed: %-13s" [bytesize $now {} "%6.1f"] "[bytesize $speed {} "%.1f"]/s"]
+                        unprogress_bar $now 20 $barprefix $barsuffix
+                    }
+                }
+            }
+        }
+        finish {
+            # erase to start of line
+            ::term::ansi::send::esol
+            # return cursor to start of line
+            puts -nonewline "\r"
+            flush stdout
+        }
+    }
 
+    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 ""} {suffix ""}} {
+    # 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 ""
+    for {set i 0} {$i < $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 "\u258c\u258c"
+    }
+
+    if {$remainder == 0 && $fullfields < $halfwidth} {
+        append progressbar " "
+    } elseif {$remainder == 1} {
+        # U+258F LEFT ONE EIGHTH BLOCK
+        append progressbar "\u258f"
+    } elseif {$remainder == 2} {
+        # U+258E LEFT ONE QUARTER BLOCK
+        append progressbar "\u258e"
+    } elseif {$remainder == 3} {
+        # U+258D LEFT THREE EIGHTHS BLOCK
+        append progressbar "\u258d"
+    } elseif {$remainder == 4} {
+        # U+258C LEFT HALF BLOCK
+        append progressbar "\u258c"
+    } elseif {$remainder == 5} {
+        # U+258B LEFT FIVE EIGHTHS BLOCK
+        append progressbar "\u258b"
+    } elseif {$remainder == 6} {
+        # U+258A LEFT THREE QUARTERS BLOCK
+        append progressbar "\u258a"
+    } elseif {$remainder == 7} {
+        # U+2589 LEFT SEVEN EIGHTHS BLOCK
+        append progressbar "\u2589"
+    }
+
+    for {set i [expr {[string length $progressbar]}]} {$i < [expr {2 * $halfwidth}]} {incr i} {
+        append progressbar " "
+    }
+    set percentagesuffix [format " %5.1f %%" $percent]
+
+    puts -nonewline "\r${prefix}\[${progressbar}\]${percentagesuffix}${suffix}"
+    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 ""} {suffix ""}} {
+    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 ""
+
+    for {set i 0} {$i < [expr {2 * $halfwidth}]} {incr i} {
+        if {[expr $i % $numstates] == ${_port_progress_unprogressbar_state}} {
+            # U+2022 BULLET
+            append progressbar "\u2022"
+        } else {
+            append progressbar " "
+        }
+    }
+
+    puts -nonewline "\r${prefix}\[${progressbar}\]${suffix}"
+    flush stdout
+}
+
+
 ##########################################
 # Main
 ##########################################
@@ -4835,6 +5000,10 @@
     exit 1
 }
 
+if {[isatty stdout] && (![info exists ui_options(ports_quiet)] || $ui_options(ports_quiet) ne "yes")} {
+    set ui_options(progress_download) port_progress_download
+}
+
 # Get arguments remaining after option processing
 set remaining_args [lrange $cmd_argv $cmd_argn end]
 

Modified: trunk/base/src/port1.0/portfetch.tcl
===================================================================
--- 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)
@@ -502,7 +502,11 @@
         lappend fetch_options "--remote-time"
     }
     if {$portverbose eq "yes"} {
-        lappend fetch_options "-v"
+        lappend fetch_options "--progress"
+        lappend fetch_options "builtin"
+    } elseif {[llength [info commands ui_progress_download]] > 0} {
+        lappend fetch_options "--progress"
+        lappend fetch_options "ui_progress_download"
     }
     set sorted no
 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/macports-changes/attachments/20140213/70da082e/attachment-0001.html>


More information about the macports-changes mailing list