home *** CD-ROM | disk | FTP | other *** search
/ Geek Gadgets 1 / ADE-1.bin / ade-dist / ncftp-2.3.0-base.tgz / ncftp-2.3.0-base.tar / contrib / ncftp / Progress.c < prev    next >
C/C++ Source or Header  |  1995-11-26  |  23KB  |  842 lines

  1. /* Progress.c */
  2.  
  3. #include "Sys.h"
  4. #include "Curses.h"
  5.  
  6. #include "Util.h"
  7. #include "Cmds.h"
  8. #include "Xfer.h"
  9. #include "Progress.h"
  10. #include "GetPass.h"
  11. #include "Bookmark.h"
  12. #include "Main.h"
  13.  
  14. /* NOTE: There is more commentary at the bottom of this source file. */
  15.  
  16. /* The progress meter the user requested us to use.  We may not actually be
  17.  * able to use this one, if certain conditions are not met.
  18.  */
  19. int gWhichProgMeter = PROGRESS;
  20.  
  21. /* Before each report, this is set to the elapsed time between the
  22.  * starting and current time.
  23.  */
  24. struct timeval gElapsedTVal;
  25.                                         
  26. /* These are the different progress meter functions the user can
  27.  * choose from.  The order must correspond to the equates in progress.h!
  28.  */
  29. ProgressMeterProc gProgressMeterProcs[kPrLast + 1] = {
  30.     PrNone,
  31.     PrPercent,
  32.     PrPhilBar,
  33.     PrKBytes,
  34.     PrDots,
  35.     PrStatBar
  36. };
  37.  
  38. long gTotalXferHSeconds = 0L, gTotalXferKiloBytes = 0L;
  39.  
  40. #ifdef SIGTSTP
  41. static int gStoppedInProgressReport;
  42. #endif
  43.  
  44. extern int gVerbosity, gIsFromTTY, gScreenWidth;
  45. extern FILE *gLogFile;
  46. extern longstring gRemoteCWD;
  47. extern Bookmark gRmtInfo;
  48. extern int gConsecutiveTimeouts;
  49.  
  50. #ifdef USE_CURSES
  51. extern int gWinInit;
  52. extern WINDOW *gListWin;
  53. void WAttr(WINDOW *w, int attr, int on);
  54. #endif    /* USE_CURSES */
  55.  
  56. #ifdef SYSLOG
  57. extern UserInfo gUserInfo;
  58. extern string gActualHostName;
  59. #endif
  60.  
  61. /* Difftime(), only for timeval structures.  */
  62. void TimeValSubtract(struct timeval *tdiff, struct timeval *t1, struct timeval *t0)
  63. {
  64.     tdiff->tv_sec = t1->tv_sec - t0->tv_sec;
  65.     tdiff->tv_usec = t1->tv_usec - t0->tv_usec;
  66.     if (tdiff->tv_usec < 0) {
  67.         tdiff->tv_sec--;
  68.         tdiff->tv_usec += 1000000;
  69.     }
  70. }                                       /* TimeValSubtract */
  71.  
  72.  
  73.  
  74. /* Computes how fast the transfer went. */
  75. double TransferRate(
  76.     long bytes,
  77.     struct timeval *start,
  78.     struct timeval *end,
  79.     char **units,
  80.     double *elapsedSecs
  81. )
  82. {
  83.     struct timeval td;
  84.     double unitsPerSec;
  85.  
  86.     TimeValSubtract(&td, end, start);
  87.     *elapsedSecs = td.tv_sec + ((double) td.tv_usec / 1000000.0);
  88.     
  89.     unitsPerSec = 0.0;
  90.     *units = "Bytes/s";
  91.  
  92.     if (*elapsedSecs > 0.0) {
  93.         unitsPerSec = bytes / *elapsedSecs;
  94.         if (unitsPerSec > (1024.0 * 1024.0)) {
  95.             unitsPerSec /= (1024.0 * 1024.0);
  96.             *units = "MB/s";
  97.         } else if (unitsPerSec > 1024.0) {
  98.             unitsPerSec /= 1024.0;
  99.             *units = "kB/s";
  100.         }
  101.     }
  102.     
  103.     return (unitsPerSec);
  104. }    /* TransferRate */
  105.  
  106.  
  107.  
  108.  
  109. int PrNone(XferSpecPtr xp, int mode)
  110. {
  111.     xp->doReports = 0;
  112.     if (mode == kPrEndMsg && gIsFromTTY)
  113.         return kPrWantStatsMsg;
  114.     return 0;
  115. }    /* PrNone */
  116.  
  117.  
  118.  
  119.  
  120. /* This progress meter function displays the percentage of the file
  121.  * we've transferred so far;  it's simple and unobtrusive.
  122.  */
  123. int PrPercent(XferSpecPtr xp, int mode)
  124. {
  125.     int result = kPrWantStatsMsg;
  126.     int perc;
  127.     
  128.     switch (mode) {
  129.         case kPrInitMsg:
  130.             EPrintF("%s:     ", xp->localFileName);
  131.             break;
  132.         case kPrUpdateMsg:
  133.             perc = (int) (100 * xp->frac);
  134.             EPrintF("\b\b\b\b%3d%%", perc);
  135.             break;
  136.         case kPrEndMsg:
  137.             result = kPrWantStatsMsg;
  138.             EPrintF("\b\b\b\b");
  139.             break;
  140.     }
  141.     return result;
  142. }    /* PrPercent */
  143.  
  144.  
  145.  
  146.  
  147. /* This one is the brainchild of my comrade, Phil Dietz.  It shows the
  148.  * progress as a fancy bar graph.
  149.  */
  150. int PrPhilBar(XferSpecPtr xp, int mode)
  151. {
  152.     int result = kPrWantStatsMsg;
  153.     int perc;
  154.     long s;
  155.     int curBarLen;
  156.     static int maxBarLen;
  157.     str64 spec1, spec3;
  158.     static string bar;
  159.     int i;
  160.     int secsLeft, minLeft;
  161.  
  162.     switch (mode) {
  163.         case kPrInitMsg:
  164.             EPrintF("%s file: %s \n",
  165.                 NETREADING(xp) ? "Receiving" : "Sending",
  166.                 xp->localFileName
  167.             );
  168.             
  169.             for (i=0; i<sizeof(bar) - 1; i++)
  170.                 bar[i] = '=';
  171.             bar[i] = '\0';
  172.  
  173.             /* Compute the size of the bar itself.  This sits between
  174.              * two numbers, one on each side of the screen.  So the
  175.              * bar length will vary, depending on how many digits we
  176.              * need to display the size of the file.
  177.              */
  178.             maxBarLen = gScreenWidth - 1 - 28;
  179.             for (s = xp->expectedSize; s > 0; s /= 10L)
  180.                 maxBarLen--;
  181.             
  182.             /* Create a specification we can hand to printf. */
  183.             (void) sprintf(spec1, "      0 %%%ds %%ld bytes. ETA: --:--",
  184.                 maxBarLen);
  185.                 
  186.             /* Print the first invocation, which is an empty graph
  187.              * plus the other stuff.
  188.              */
  189.             EPrintF(spec1, " ", xp->expectedSize);
  190.             break;
  191.         case kPrUpdateMsg:
  192.             /* Compute how much of the bar should be colored in. */
  193.             curBarLen = (int) (xp->frac * (double)maxBarLen);
  194.  
  195.             /* Colored portion must be at least one character so
  196.              * the spec isn't '%0s' which would screw the right side
  197.              * of the indicator.
  198.              */
  199.             if (curBarLen < 1)
  200.                 curBarLen = 1;
  201.             
  202.             bar[curBarLen - 1] = '>';
  203.             bar[curBarLen] = '\0';
  204.  
  205.             /* Make the spec, so we can print the bar and the other stuff. */
  206.             STRNCPY(spec1, "\r%3d%%  0 ");
  207.             (void) sprintf(spec3, "%%%ds %%ld bytes. %s%%3d:%%02d", 
  208.                 maxBarLen - curBarLen,
  209.                 (gConsecutiveTimeouts == 0) ? "ETA:" : "[T] "
  210.             );
  211.             
  212.             /* We also show the percentage as a number at the left side. */
  213.             perc = (int) (100.0 * xp->frac);
  214.             
  215.             /* Guess how much time is remaining in the transfer, based on
  216.              * the current transfer statistics.
  217.              */
  218.             secsLeft = (int) ((xp->bytesLeft / xp->bytesPerSec) + 0.5);
  219.             minLeft = secsLeft / 60;
  220.             secsLeft = secsLeft - (minLeft * 60);
  221.             if (minLeft > 999) {
  222.                 minLeft = 999;
  223.                 secsLeft = 59;
  224.             }
  225.             
  226.             /* Print the updated information. */
  227. #ifdef USE_CURSES
  228.             if (gWinInit) {
  229.                 EPrintF(spec1, perc);    
  230.                 WAttr(gListWin, kReverse, 1);
  231.                 EPrintF("%s", bar);
  232.                 WAttr(gListWin, kReverse, 0);
  233.                 EPrintF(spec3,
  234.                     "",
  235.                     xp->expectedSize,
  236.                     minLeft,
  237.                     secsLeft
  238.                 );
  239.             } else
  240. #endif    /* USE_CURSES */
  241.             {
  242.                 EPrintF(spec1, perc);    
  243.                 EPrintF("%s", bar);
  244.                 EPrintF(spec3,
  245.                     "",
  246.                     xp->expectedSize,
  247.                     minLeft,
  248.                     secsLeft
  249.                 );
  250.             }
  251.  
  252.             bar[curBarLen - 1] = '=';
  253.             bar[curBarLen] = '=';
  254.  
  255.             break;
  256.         case kPrEndMsg:
  257.             result = kPrWantStatsMsg;
  258.             EPrintF("\n");
  259.             break;
  260.     }
  261.     return result;
  262. }    /* PrPhilBar */
  263.  
  264.  
  265.  
  266. static double
  267. FileSize(double size, char **uStr0, double *uMult0)
  268. {
  269.     double uMult, uTotal;
  270.     char *uStr;
  271.  
  272.     if (size > kGigabyte) {
  273.         uStr = "GB";
  274.         uMult = kGigabyte;
  275.     } else if (size > kMegabyte) {
  276.         uStr = "MB";
  277.         uMult = kMegabyte;
  278.     } else if (size > kKilobyte) {
  279.         uStr = "kB";
  280.         uMult = 1024;
  281.     } else {
  282.         uStr = "B";
  283.         uMult = 1;
  284.     }
  285.     if (uStr0 != NULL)
  286.         *uStr0 = uStr;
  287.     if (uMult0 != NULL)
  288.         *uMult0 = uMult;
  289.     uTotal = size / ((double) uMult);
  290.     return (uTotal);
  291. }    /* FileSize */
  292.  
  293.  
  294.  
  295.  
  296. int PrStatBar(XferSpecPtr xp, int mode)
  297. {
  298.     int result = kPrWantStatsMsg;
  299.     double rate, done;
  300.     int secLeft, minLeft;
  301.     char *rStr;
  302.     static char *uStr;
  303.     static double uTotal, uMult;
  304.     char localName[32];
  305.     string line;
  306.     int i;
  307.  
  308.     switch (mode) {
  309.         case kPrInitMsg:
  310.             uTotal = FileSize((double) xp->expectedSize, &uStr, &uMult);
  311.  
  312.             /* Leave room for a ':' and '\0'. */
  313.             AbbrevStr(localName, xp->localFileName, 30, 0);
  314.             STRNCAT(localName, ":");
  315.             EPrintF("%-32s", localName);
  316.             break;
  317.         case kPrUpdateMsg:
  318.             /* Guess how much time is remaining in the transfer, based on
  319.              * the current transfer statistics.
  320.              */
  321.             secLeft = (int) ((xp->bytesLeft / xp->bytesPerSec) + 0.5);
  322.             minLeft = secLeft / 60;
  323.             secLeft = secLeft - (minLeft * 60);
  324.             if (minLeft > 999) {
  325.                 minLeft = 999;
  326.                 secLeft = 59;
  327.             }
  328.  
  329.             rate = FileSize(xp->bytesPerSec, &rStr, NULL);
  330.             done = (double) xp->bytesTransferred / uMult;
  331.  
  332.             AbbrevStr(localName, xp->localFileName, sizeof(localName) - 2, 0);
  333.             STRNCAT(localName, ":");
  334.  
  335.             sprintf(line, "%-32s   %5.1f/%.1f %s   %5.1f %s/s   ETA %3d:%02d%s",
  336.                 localName,
  337.                 done,
  338.                 uTotal,
  339.                 uStr,
  340.                 rate,
  341.                 rStr,
  342.                 minLeft,
  343.                 secLeft,
  344.                 (gConsecutiveTimeouts > 0) ? "  [T]" : ""
  345.             );
  346.  
  347.             /* Pad the rest of the line with spaces, to erase any
  348.              * stuff that might have been left over from the last
  349.              * update.
  350.              */
  351.             for (i=strlen(line); i < gScreenWidth - 2; i++)
  352.                 line[i] = ' ';
  353.             line[i] = '\0';
  354.  
  355.             /* Print the updated information. */
  356.             EPrintF("\r%s", line);
  357.  
  358.             break;
  359.         case kPrEndMsg:
  360.             result = kPrWantStatsMsg;
  361.             EPrintF("\r");
  362.             break;
  363.     }
  364.     return result;
  365. }    /* PrStatBar */
  366.  
  367.  
  368.  
  369.  
  370. /* This one is handy when we don't know the size of the file we're
  371.  * transferring, but we still want to have a progress meter.
  372.  */
  373. int PrKBytes(XferSpecPtr xp, int mode)
  374. {
  375.     int result = 0;
  376.  
  377.     switch (mode) {
  378.         case kPrInitMsg:
  379.             EPrintF("%s:       ", xp->localFileName);
  380.             break;
  381.         case kPrUpdateMsg:
  382.             EPrintF("\b\b\b\b\b\b%5ldK", (xp->bytesTransferred +
  383.                 xp->startPoint) / 1024);
  384.             break;
  385.         case kPrEndMsg:
  386.             result = kPrWantStatsMsg;
  387.             EPrintF("\b\b\b\b\b\b");
  388.             break;
  389.     }
  390.     return result;
  391. }    /* PrKBytes */
  392.  
  393.  
  394.  
  395.  
  396. /* This progress meter spews as little output as possible.  It uses
  397.  * no backspaces or ANSI escapes nor does it ask for transfer stats to
  398.  * be printed.
  399.  */
  400. int PrDots(XferSpecPtr xp, int mode)
  401. {
  402.     static int dotsPrinted;
  403.     int newDots;
  404.  
  405.     switch (mode) {
  406.         case kPrInitMsg:
  407.             dotsPrinted = 0;
  408.             EPrintF("%s: ", xp->localFileName);
  409.             break;
  410.         case kPrUpdateMsg:
  411.             if (xp->expectedSize <= 0)
  412.                 newDots = 10;
  413.             else
  414.                 newDots = (LOCALSIZE(xp) * 10 / xp->expectedSize) + 1;
  415.             while ((dotsPrinted < newDots) && (dotsPrinted < 10)) {
  416.                 EPrintF(".");
  417.                 dotsPrinted++;
  418.             }
  419.             break;
  420.         case kPrEndMsg:
  421.             for (; dotsPrinted < 10; dotsPrinted++)
  422.                 EPrintF(".");
  423.             EPrintF("\n");
  424.             break;
  425.     }
  426.     return 0;
  427. }    /* PrDots */
  428.  
  429.  
  430.  
  431.  
  432. /* This determines which progress meter we can use, and sends an
  433.  * init message to the progress meter function to be used.  Note
  434.  * that we may have to use an alternative progress meter if the
  435.  * one the user requested won't work under the current conditions (like
  436.  * not being able to get the remote file size would disable any
  437.  * prog meter that needs to know that).
  438.  *
  439.  * This routine takes care of turning off echoing to stdin and flushes
  440.  * the stderr stream, so individual progress meter functions need not
  441.  * worry about that.
  442.  */
  443. int StartProgress(XferSpecPtr xp)
  444. {
  445.     int progMeterInUse;
  446.  
  447.     /* If the user doesn't want any unnecessary output, or is in the
  448.      * background, don't have progress reports at all.  Also don't
  449.      * do reports if the xp said explicitly not to do.  For example,
  450.      * we don't want any reports for directory listings.
  451.      */
  452.     if ((gVerbosity == kQuiet) || (!xp->doReports) || (!InForeGround())) {
  453.         progMeterInUse = kPrNone;
  454.         xp->doReports = 0;
  455.     } else {
  456.         progMeterInUse =  gWhichProgMeter;
  457.         
  458.         /* If the progress meter number is out of range, use a default value. */
  459.         if ((progMeterInUse > kPrLast) || (progMeterInUse < 0))
  460.             progMeterInUse = PROGRESS;
  461.         
  462.         /* If we couldn't determine the size of the file in advance,
  463.          * we can't use any progress meter that needs to know the size.
  464.          */
  465.         if (xp->expectedSize == kSizeUnknown)
  466.             progMeterInUse = kPrKBytes;    /* Only one that doesn't need size. */
  467.     }
  468.  
  469.     /* Get a pointer to the progress meter function to use. */
  470.     xp->prProc = gProgressMeterProcs[progMeterInUse];
  471.  
  472.     /* Make a note of the start time, so we can see how long this takes. */
  473.     (void) Gettimeofday(&xp->startTime);
  474.     
  475.     /* Initialize the seconds counter to the current time.  We don't do
  476.      * a progress report after each block read.  Instead, we wait at least
  477.      * 'kDelaySeconds' seconds before attempting an update.  But this time
  478.      * we set it so we do an update after the very first block.
  479.      */
  480.     xp->timeOfNextUpdate = xp->startTime.tv_sec;
  481.     
  482.     /* This should have been zeroed automatically when you initialized
  483.      * the structure, but for completeness, do it ourselves here.
  484.      */
  485.     xp->bytesTransferred = 0;
  486.  
  487.     (*xp->prProc)(xp, kPrInitMsg);
  488.     
  489.     /* The progress report proc may decide for itself whether progress
  490.      * reports may be used, so don't do the following if progress
  491.      * reports got turned off at the initialization step.
  492.      */
  493.     if (xp->doReports) {
  494.         FlushListWindow();
  495.         Echo(stdin, 0);
  496.     }
  497.     
  498.     /* Just a nice way for us to quickly tell which one we're using,
  499.      * without looking at function pointers.
  500.      */
  501.     xp->progMeterInUse = progMeterInUse;
  502.     return (progMeterInUse);
  503. }                                       /* StartProgress */
  504.  
  505.  
  506.  
  507. #ifdef SIGTSTP
  508. static void ProgressSuspsend(int ignored)
  509. {
  510.     gStoppedInProgressReport = 1;
  511. }    /* ProgressSuspsend */
  512. #endif
  513.  
  514.  
  515.  
  516.  
  517. /* This should be called after each "block" in the transfer.  We check
  518.  * here to see if we should bother updating the progress meter.  We
  519.  * won't update if we had already updated within the last second or so.
  520.  */
  521. void ProgressReport(XferSpecPtr xp, int forceUpdate)
  522. {
  523.     double frac;
  524. #ifdef SIGTSTP
  525.     Sig_t sts;
  526. #endif
  527.  
  528.     if (xp->doReports) {
  529.         /* Check the current time. */
  530.         (void) Gettimeofday(&xp->endTime);
  531.         
  532.         /* Won't update unless it's past the 'timeOfNextUpdate'
  533.          * or we got a message saying to do your last update.
  534.          */
  535.         if ((xp->endTime.tv_sec > xp->timeOfNextUpdate) || forceUpdate) {
  536.             /* Won't do updates anymore if we get backgrounded. */
  537.             xp->doReports = InForeGround();
  538.             if (xp->doReports != 0) {
  539. #ifdef SIGTSTP
  540.                 /* The user could hit ^Z right when we are trying to do
  541.                  * a progress report.  This could mean that as soon as the
  542.                  * user put the program in the background, we would output
  543.                  * the progress report, which would suspend the process
  544.                  * because of tty output.
  545.                  *
  546.                  * We try to avoid that scenario by temporarily putting off
  547.                  * the ^Z, doing the progress report, then sending us the
  548.                  * suspend signal again.
  549.                  */
  550.                 sts = SIGNAL(SIGTSTP, SIG_IGN);
  551.                 if (sts != SIG_IGN) {
  552.                     gStoppedInProgressReport = 0;
  553.                     SIGNAL(SIGTSTP, ProgressSuspsend);
  554.                 }
  555. #endif
  556.                 /* Figure out how long the transfer has been going. */
  557.                 TimeValSubtract(&gElapsedTVal, &xp->endTime,
  558.                     &xp->startTime);
  559.                 
  560.                 /* Get current transfer duration. */
  561.                 xp->secsElap = (gElapsedTVal.tv_usec / 1000000.0)
  562.                     + gElapsedTVal.tv_sec;
  563.  
  564.                 /* Get current transfer rate. */
  565.                 if ((xp->secsElap <= 0.0) || (xp->bytesTransferred == 0)) {
  566.                     xp->bytesPerSec = 1.0;        /* Don't set to 0. */
  567.                 } else {
  568.                     xp->bytesPerSec = ((double) xp->bytesTransferred)
  569.                         / xp->secsElap;
  570.                 }
  571.  
  572.                 /* Compute how much we've done so far, if we can. */
  573.                 if (xp->expectedSize > 0) {
  574.                     xp->bytesLeft = xp->expectedSize - LOCALSIZE(xp);
  575.  
  576.                     frac = (double) LOCALSIZE(xp)
  577.                         / (double) xp->expectedSize;
  578.                     if (frac > 1.0)
  579.                         frac = 1.0;
  580.                     else if (frac < 0.0)
  581.                         frac = 0.0;
  582.                     xp->frac = frac;
  583.                 } else {
  584.                     xp->frac = 0.0;
  585.                 }
  586.                 
  587.                 /* Have this progress meter do its thing. */
  588.                 (*xp->prProc)(xp, kPrUpdateMsg);
  589.             
  590.                 FlushListWindow();
  591.                 /* Compute the next update time. */
  592.                 xp->timeOfNextUpdate = xp->endTime.tv_sec + kDelaySeconds - 1;
  593. #ifdef SIGTSTP
  594.                 if (sts != SIG_IGN) {
  595.                     SIGNAL(SIGTSTP, sts);
  596.                     if (gStoppedInProgressReport != 0)
  597.                         kill(getpid(), SIGTSTP);
  598.                 }
  599. #endif
  600.                 /* Figure out how long the transfer has been going. */
  601.             } else {
  602.                 return;
  603.             }
  604.         }
  605.         
  606.         /* Won't do reports if the user isn't logged in to see them. */
  607.         xp->doReports = UserLoggedIn();
  608.     }
  609. }                                       /* ProgressReport */
  610.  
  611.  
  612.  
  613.  
  614. /* This routine should be called after a transfer finishes, even if no
  615.  * progress reports were done.  Besides cleaning up the progress stuff,
  616.  * we also do our logging here.
  617.  */
  618. void EndProgress(XferSpecPtr xp)
  619. {
  620.     double elapsedTime, xRate, xferred;
  621.     char *unitStr;
  622.     char *shortName;
  623.     string statMsg;
  624.     longstring fullRemote;
  625.     long kb, hsecs;
  626.     int wantStats;
  627.     int localFileIsStdout;
  628.  
  629.     wantStats = 0;
  630.     if ((xp->doReports) && (xp->bytesTransferred > 0) && (gVerbosity != kQuiet)) {
  631.         ProgressReport(xp, kPrLastUpdateMsg);
  632.         wantStats = (InForeGround()) &&
  633.             ((*xp->prProc)(xp, kPrEndMsg) == kPrWantStatsMsg);
  634.     }
  635.     (void) Gettimeofday(&xp->endTime);
  636.  
  637.     /* Compute transfer stats. */
  638.     xRate = TransferRate(
  639.         xp->bytesTransferred,
  640.         &xp->startTime,
  641.         &xp->endTime,
  642.         &unitStr,
  643.         &elapsedTime
  644.     );
  645.  
  646.     /* Print the stats, if requested. */
  647.     if (wantStats) {
  648.         shortName = strrchr(xp->localFileName, '/');
  649.         if (shortName == NULL)
  650.             shortName = xp->localFileName;
  651.         else
  652.             shortName++;
  653.         sprintf(statMsg, "%s:  %ld bytes %s%s in %.2f seconds",
  654.             shortName,
  655.             xp->bytesTransferred,
  656.             NETREADING(xp) ? "received" : "sent",
  657.             xp->startPoint ? " and appended to existing file" : "",
  658.             elapsedTime
  659.         );
  660.         if (xRate > 0.0) {
  661.             sprintf(statMsg + strlen(statMsg), ", %.2f %s",
  662.                 xRate,
  663.                 unitStr
  664.             );
  665.         }
  666.         STRNCAT(statMsg, ".");
  667.     
  668.         /* Make sure echoing is back on! */
  669.         Echo(stdin, 1);
  670.  
  671.         /* Make sure the rest of the line is padded with spaces, so it will
  672.          * erase junk that may have been leftover from a progress meter.
  673.          */
  674.         EPrintF("%-79s\n", statMsg);
  675.         FlushListWindow();
  676.     } else {
  677.         if (xRate > 0.0) {
  678.             DebugMsg("%ld bytes transferred in %.2f seconds, %.2f %s.\n",
  679.                 xp->bytesTransferred,
  680.                 elapsedTime,
  681.                 xRate,
  682.                 unitStr
  683.             );
  684.         } else {
  685.             DebugMsg("%ld bytes transferred in %.2f seconds.\n",
  686.                 xp->bytesTransferred,
  687.                 elapsedTime
  688.             );
  689.         }
  690.     }
  691.  
  692.     /* Only log stuff if there was a remote filename specified.
  693.      * We don't want to log directory listings or globbings.
  694.      */
  695.     if ((xp->remoteFileName != NULL)) {
  696.         /* Get kilobytes transferred, rounding to the nearest kB. */
  697.         kb = ((long) xp->bytesTransferred + 512L) / 1024L;
  698.         OverflowAdd(&gTotalXferKiloBytes, kb);
  699.         OverflowAdd(&gRmtInfo.xferKbytes, kb);
  700.  
  701.         /* Get hundredths of seconds, rounded up to nearest. */
  702.         hsecs = (long) (100.0 * (elapsedTime + 0.0050));
  703.         OverflowAdd(&gTotalXferHSeconds, hsecs);
  704.         OverflowAdd(&gRmtInfo.xferHSeconds, hsecs);
  705.         if (gTotalXferHSeconds < 0)
  706.             gTotalXferHSeconds = 1L;
  707.         if (gRmtInfo.xferHSeconds <= 0)
  708.             gRmtInfo.xferHSeconds = 1L;
  709.  
  710.         localFileIsStdout =
  711.             (STREQ(xp->remoteFileName, kLocalFileIsStdout));
  712.         /* If a simple path is given, try to log the full path. */
  713.         if ((xp->remoteFileName[0] == '/') || (localFileIsStdout)) {
  714.             /* Use what we had in the xp. */
  715.             STRNCPY(fullRemote, xp->remoteFileName);
  716.         } else {
  717.             /* Make full path by appending what we had in the xp
  718.              * to the current remote directory.
  719.              */
  720.             STRNCPY(fullRemote, gRemoteCWD);
  721.             STRNCAT(fullRemote, "/");
  722.             STRNCAT(fullRemote, xp->remoteFileName);
  723.         }
  724.     
  725.         /* Save transfers to the user's logfile.  We only log something to
  726.          * the user log if we are actually saving a file;  we don't log
  727.          * to the user log if we are piping the remote output into something,
  728.          * or dumping it to stdout.
  729.          */
  730.         if ((gLogFile != NULL) && (!localFileIsStdout)) {
  731.             xferred = FileSize((double) xp->bytesTransferred, &unitStr, NULL);
  732.             (void) fprintf(gLogFile, "  %-3s  %6.2f %-2s  ftp://%s%s\n",
  733.                 NETREADING(xp) ? "get" : "put",
  734.                 xferred,
  735.                 unitStr,
  736.                 gRmtInfo.name,
  737.                 fullRemote
  738.             );
  739.             fflush(gLogFile);
  740.         }
  741.  
  742. #ifdef SYSLOG
  743.         {
  744.         longstring infoPart1;
  745.  
  746.         /* Some syslog()'s can't take an unlimited number of arguments,
  747.          * so shorten our call to syslog to 5 arguments total.
  748.          */
  749.         STRNCPY(infoPart1, gUserInfo.userName);
  750.         if (NETREADING(xp)) {
  751.             STRNCAT(infoPart1, " received ");
  752.             STRNCAT(infoPart1, fullRemote);    /* kLocalFileIsStdout is ok. */
  753.             STRNCAT(infoPart1, " as ");
  754.             STRNCAT(infoPart1, xp->localFileName);
  755.             STRNCAT(infoPart1, " from ");
  756.         } else {
  757.             STRNCAT(infoPart1, " sent ");
  758.             STRNCAT(infoPart1, xp->localFileName);
  759.             STRNCAT(infoPart1, " as ");
  760.             STRNCAT(infoPart1, fullRemote);
  761.             STRNCAT(infoPart1, " to ");
  762.         }
  763.         STRNCAT(infoPart1, gActualHostName);
  764. #ifndef LOG_INFO
  765. #    define LOG_INFO 6        /* Don't know if this is standard! */
  766. #endif
  767.         syslog (LOG_INFO, "%s (%ld bytes).", infoPart1, xp->bytesTransferred);
  768.         }
  769. #endif  /* SYSLOG */
  770.     }
  771. }                                       /* EndProgress */
  772.  
  773. /*****************************************************************************
  774.  
  775. How progress meters are used.
  776. -----------------------------
  777.  
  778. Just before the transfer is to be started, StartProgress is called.  This is
  779. called even if the 'doReports' field is set to 0 in the XferSpec. The reason
  780. for this is because this module also handles logging, both to the system log,
  781. and to the user's log.  We have to call both StartProgress and EndProgress to
  782. make sure logging is done correctly.
  783.  
  784. StartProgress figures out which progress meter to use.  The user may have
  785. decided on a favorite, but sometimes we may not be able to use the one
  786. requested.  The classic example is when we could not get the size of the file
  787. we're transferring in advance.  Things that use percentages or bar graphs
  788. won't work in that case.
  789.  
  790. If StartProgress found that progress reports will be used, it determines
  791. which progress meter function to use, and sends it a 'kPrInitMsg' so the
  792. function can do any necessary initialization.  The progress meter function
  793. doesn't have to print anything at this point.  Character echoing is turned
  794. off at this point, so any keys typed by the user won't mess up the progress
  795. function's hard work.
  796.  
  797. During the transfer, which is taken care of in Xfer.c, we call
  798. ProgressReport.  We only bother doing this if progress reports are really on,
  799. since none of the logging stuff is done at this time.
  800.  
  801. ProgressReport does a few quick checks to see if it should call the progress
  802. meter.  Because reporting could waste too much time, we don't want to do it
  803. too often.  Instead we wait a few seconds between each actual screen update.
  804.  
  805. ProgressReport also does a few checks to see if reporting should be turned
  806. off.  We don't want any more reports if we've been put into the background,
  807. since as soon as we printed something, the job would stop, because of pending
  808. tty output.  We also don't want any more reports if the user logged out.
  809.  
  810. ProgressReport then sends a 'kPrUpdateMsg' to the progress function, which
  811. then does the screen update.
  812.  
  813. After the transfer finishes, we call EndReport.  The first thing it does, if
  814. reports are still on, is to force one last screen update.  This is needed,
  815. for example, by the bar graph meter so the bar will be completely filled.
  816.  
  817. EndReport then sends the progress function a 'kPrEndMsg' so the function can
  818. do any necessary cleanup.  After turning character echoing back on, the stats
  819. are printed if they were requested, and then the logs are updated.
  820.  
  821. Adding your own progress meters.
  822. --------------------------------
  823.  
  824. You need to create a function that answers to the 'kPrInitMsg,' 'kPrUpdateMsg,'
  825. and 'kPrEndMsg' messages.  You'll be given as parameters the XferSpecPtr in
  826. use, and a 'mode' that indicates which of the messages you received.  Have
  827. the function return 0, or kPrWantStatsMsg.
  828.  
  829. You'll need to have a look at StartProgress and see if your function has any
  830. special requirements.  You'll need to edit Progress.h and make a #define for
  831. your function.  Don't forget to change kPrLast, and add a function prototype
  832. in the header too.  Lastly, add an entry to 'gProgressMeterProcs' with your
  833. function corresponding to whatever value you #defined in the header.
  834.  
  835. In order for your progress meter to be recognized by the Preferences screen,
  836. edit Prefs.c and add the name of your meter in the "case kProgressPrefsWinItem:"
  837. portion of GetPrefSetting().
  838.  
  839. *****************************************************************************/
  840.  
  841. /* eof Progress.c */
  842.