LCOV - code coverage report
Current view: top level - port - cpl_progress.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 62 89 69.7 %
Date: 2026-01-03 03:21:54 Functions: 5 5 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  CPL - Common Portability Library
       4             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       5             :  * Purpose:  Progress function implementations.
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2013, Frank Warmerdam
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_progress.h"
      14             : 
      15             : #include <cmath>
      16             : #include <cstdio>
      17             : #include <ctime>
      18             : 
      19             : #include <algorithm>
      20             : 
      21             : #include "cpl_conv.h"
      22             : #include "cpl_string.h"
      23             : 
      24             : /************************************************************************/
      25             : /*                         GDALDummyProgress()                          */
      26             : /************************************************************************/
      27             : 
      28             : /**
      29             :  * \brief Stub progress function.
      30             :  *
      31             :  * This is a stub (does nothing) implementation of the GDALProgressFunc()
      32             :  * semantics.  It is primarily useful for passing to functions that take
      33             :  * a GDALProgressFunc() argument but for which the application does not want
      34             :  * to use one of the other progress functions that actually do something.
      35             :  */
      36             : 
      37      727469 : int CPL_STDCALL GDALDummyProgress(double /* dfComplete */,
      38             :                                   const char * /* pszMessage */,
      39             :                                   void * /* pData */)
      40             : {
      41      727469 :     return TRUE;
      42             : }
      43             : 
      44             : /************************************************************************/
      45             : /*                         GDALScaledProgress()                         */
      46             : /************************************************************************/
      47             : typedef struct
      48             : {
      49             :     GDALProgressFunc pfnProgress;
      50             :     void *pData;
      51             :     double dfMin;
      52             :     double dfMax;
      53             : } GDALScaledProgressInfo;
      54             : 
      55             : /**
      56             :  * \brief Scaled progress transformer.
      57             :  *
      58             :  * This is the progress function that should be passed along with the
      59             :  * callback data returned by GDALCreateScaledProgress().
      60             :  */
      61             : 
      62      960313 : int CPL_STDCALL GDALScaledProgress(double dfComplete, const char *pszMessage,
      63             :                                    void *pData)
      64             : 
      65             : {
      66      960313 :     GDALScaledProgressInfo *psInfo =
      67             :         reinterpret_cast<GDALScaledProgressInfo *>(pData);
      68             : 
      69             :     // Optimization if GDALCreateScaledProgress() provided with
      70             :     // GDALDummyProgress.
      71      960313 :     if (psInfo == nullptr)
      72      498981 :         return TRUE;
      73             : 
      74      922664 :     return psInfo->pfnProgress(dfComplete * (psInfo->dfMax - psInfo->dfMin) +
      75      461332 :                                    psInfo->dfMin,
      76      461332 :                                pszMessage, psInfo->pData);
      77             : }
      78             : 
      79             : /************************************************************************/
      80             : /*                      GDALCreateScaledProgress()                      */
      81             : /************************************************************************/
      82             : 
      83             : /**
      84             :  * \brief Create scaled progress transformer.
      85             :  *
      86             :  * Sometimes when an operations wants to report progress it actually
      87             :  * invokes several subprocesses which also take GDALProgressFunc()s,
      88             :  * and it is desirable to map the progress of each sub operation into
      89             :  * a portion of 0.0 to 1.0 progress of the overall process.  The scaled
      90             :  * progress function can be used for this.
      91             :  *
      92             :  * For each subsection a scaled progress function is created and
      93             :  * instead of passing the overall progress func down to the sub functions,
      94             :  * the GDALScaledProgress() function is passed instead.
      95             :  *
      96             :  * @param dfMin the value to which 0.0 in the sub operation is mapped.
      97             :  * @param dfMax the value to which 1.0 is the sub operation is mapped.
      98             :  * @param pfnProgress the overall progress function.
      99             :  * @param pData the overall progress function callback data.
     100             :  *
     101             :  * @return pointer to pass as pProgressArg to sub functions.  Should be freed
     102             :  * with GDALDestroyScaledProgress().
     103             :  *
     104             :  * Example:
     105             :  *
     106             :  * \code
     107             :  *   int MyOperation( ..., GDALProgressFunc pfnProgress, void *pProgressData );
     108             :  *
     109             :  *   {
     110             :  *       void *pScaledProgress;
     111             :  *
     112             :  *       pScaledProgress = GDALCreateScaledProgress( 0.0, 0.5, pfnProgress,
     113             :  *                                                   pProgressData );
     114             :  *       GDALDoLongSlowOperation( ..., GDALScaledProgress, pScaledProgress );
     115             :  *       GDALDestroyScaledProgress( pScaledProgress );
     116             :  *
     117             :  *       pScaledProgress = GDALCreateScaledProgress( 0.5, 1.0, pfnProgress,
     118             :  *                                                   pProgressData );
     119             :  *       GDALDoAnotherOperation( ..., GDALScaledProgress, pScaledProgress );
     120             :  *       GDALDestroyScaledProgress( pScaledProgress );
     121             :  *
     122             :  *       return ...;
     123             :  *   }
     124             :  * \endcode
     125             :  */
     126             : 
     127      859005 : void *CPL_STDCALL GDALCreateScaledProgress(double dfMin, double dfMax,
     128             :                                            GDALProgressFunc pfnProgress,
     129             :                                            void *pData)
     130             : 
     131             : {
     132      859005 :     if (pfnProgress == nullptr || pfnProgress == GDALDummyProgress)
     133      845924 :         return nullptr;
     134             : 
     135             :     GDALScaledProgressInfo *psInfo = static_cast<GDALScaledProgressInfo *>(
     136       13081 :         CPLCalloc(sizeof(GDALScaledProgressInfo), 1));
     137             : 
     138       13081 :     psInfo->pData = pData;
     139       13081 :     psInfo->pfnProgress = pfnProgress;
     140       13081 :     psInfo->dfMin = dfMin;
     141       13081 :     psInfo->dfMax = dfMax;
     142             : 
     143       13081 :     return static_cast<void *>(psInfo);
     144             : }
     145             : 
     146             : /************************************************************************/
     147             : /*                     GDALDestroyScaledProgress()                      */
     148             : /************************************************************************/
     149             : 
     150             : /**
     151             :  * \brief Cleanup scaled progress handle.
     152             :  *
     153             :  * This function cleans up the data associated with a scaled progress function
     154             :  * as returned by GADLCreateScaledProgress().
     155             :  *
     156             :  * @param pData scaled progress handle returned by GDALCreateScaledProgress().
     157             :  */
     158             : 
     159      858451 : void CPL_STDCALL GDALDestroyScaledProgress(void *pData)
     160             : 
     161             : {
     162      858451 :     CPLFree(pData);
     163      858450 : }
     164             : 
     165             : /************************************************************************/
     166             : /*                      GDALTermProgressWidth()                         */
     167             : /************************************************************************/
     168             : 
     169             : static constexpr int GDALTermProgressWidth(int nMaxTicks, int nMajorTickSpacing)
     170             : {
     171             :     int nWidth = 0;
     172             :     for (int i = 0; i <= nMaxTicks; i++)
     173             :     {
     174             :         if (i % nMajorTickSpacing == 0)
     175             :         {
     176             :             int nPercent = (i * 100) / nMaxTicks;
     177             :             do
     178             :             {
     179             :                 nWidth++;
     180             :             } while (nPercent /= 10);
     181             :         }
     182             :         else
     183             :         {
     184             :             nWidth += 1;
     185             :         }
     186             :     }
     187             :     return nWidth;
     188             : }
     189             : 
     190             : /************************************************************************/
     191             : /*                          GDALTermProgress()                          */
     192             : /************************************************************************/
     193             : 
     194             : /**
     195             :  * \fn GDALTermProgress(double, const char*, void*)
     196             :  * \brief Simple progress report to terminal.
     197             :  *
     198             :  * This progress reporter prints simple progress report to the
     199             :  * terminal window.  The progress report generally looks something like
     200             :  * this:
     201             : 
     202             : \verbatim
     203             : 0...10...20...30...40...50...60...70...80...90...100 - done.
     204             : \endverbatim
     205             : 
     206             :  * Starting with GDAL 3.11, for tasks estimated to take more than 10 seconds,
     207             :  * an estimated remaining time is also displayed at the end. And for tasks
     208             :  * taking more than 5 seconds to complete, the total time is displayed upon
     209             :  * completion.
     210             :  *
     211             :  * Every 2.5% of progress another number or period is emitted.  Note that
     212             :  * GDALTermProgress() uses internal static data to keep track of the last
     213             :  * percentage reported and will get confused if two terminal based progress
     214             :  * reportings are active at the same time.
     215             :  *
     216             :  * The GDALTermProgress() function maintains an internal memory of the
     217             :  * last percentage complete reported in a static variable, and this makes
     218             :  * it unsuitable to have multiple GDALTermProgress()'s active either in a
     219             :  * single thread or across multiple threads.
     220             :  *
     221             :  * @param dfComplete completion ratio from 0.0 to 1.0.
     222             :  * @param pszMessage optional message.
     223             :  * @param pProgressArg ignored callback data argument.
     224             :  *
     225             :  * @return Always returns TRUE indicating the process should continue.
     226             :  */
     227             : 
     228       27297 : int CPL_STDCALL GDALTermProgress(double dfComplete,
     229             :                                  CPL_UNUSED const char *pszMessage,
     230             :                                  CPL_UNUSED void *pProgressArg)
     231             : {
     232       27297 :     constexpr int MAX_TICKS = 40;
     233       27297 :     constexpr int MAJOR_TICK_SPACING = 4;
     234       27297 :     constexpr int LENGTH_OF_0_TO_100_PROGRESS =
     235             :         GDALTermProgressWidth(MAX_TICKS, MAJOR_TICK_SPACING);
     236             : 
     237             :     const int nThisTick = std::min(
     238       27297 :         MAX_TICKS, std::max(0, static_cast<int>(dfComplete * MAX_TICKS)));
     239             : 
     240             :     // Have we started a new progress run?
     241             :     static int nLastTick = -1;
     242             :     static time_t nStartTime = 0;
     243             :     // whether estimated remaining time is displayed
     244             :     static bool bETADisplayed = false;
     245             :     // number of characters displayed during last progress call
     246             :     static int nCharacterCountLastTime = 0;
     247             :     // maximum number of characters displayed during previous calls
     248             :     static int nCharacterCountMax = 0;
     249       27297 :     if (nThisTick < nLastTick && nLastTick >= MAX_TICKS - 1)
     250             :     {
     251          10 :         bETADisplayed = false;
     252          10 :         nLastTick = -1;
     253          10 :         nCharacterCountLastTime = 0;
     254          10 :         nCharacterCountMax = 0;
     255             :     }
     256             : 
     257       27297 :     if (nThisTick <= nLastTick)
     258       20438 :         return TRUE;
     259             : 
     260        6859 :     const time_t nCurTime = time(nullptr);
     261        6859 :     if (nLastTick < 0)
     262         418 :         nStartTime = nCurTime;
     263             : 
     264        6859 :     constexpr int MIN_DELAY_FOR_ETA = 5;  // in seconds
     265        6859 :     if (nCurTime - nStartTime >= MIN_DELAY_FOR_ETA && dfComplete > 0 &&
     266             :         dfComplete < 0.5)
     267             :     {
     268           0 :         static bool bIsTTY = CPLIsInteractive(stdout);
     269           0 :         bETADisplayed = bIsTTY;
     270             :     }
     271        6859 :     if (bETADisplayed)
     272             :     {
     273           0 :         for (int i = 0; i < nCharacterCountLastTime; ++i)
     274           0 :             fprintf(stdout, "\b");
     275           0 :         nLastTick = -1;
     276           0 :         nCharacterCountLastTime = 0;
     277             : 
     278             : #ifdef _WIN32
     279             :         constexpr const char *WINDOWS_TERMINAL_ENV_VAR = "WT_SESSION";
     280             :         constexpr const char *CONEMU_ENV_VAR = "ConEmuANSI";
     281             : #endif
     282           0 :         static const bool bAllowOSC94 = CPLTestBool(CPLGetConfigOption(
     283             :             "GDAL_TERM_PROGRESS_OSC_9_4",
     284             : #ifdef _WIN32
     285             :             // Detect if we are running under Windows Terminal
     286             :             (CPLGetConfigOption(WINDOWS_TERMINAL_ENV_VAR, nullptr) != nullptr ||
     287             :              // or ConEmu
     288             :              CPLGetConfigOption(CONEMU_ENV_VAR, nullptr) != nullptr)
     289             :                 ? "YES"
     290             :                 : "NO"
     291             : #else
     292             :             "YES"
     293             : #endif
     294           0 :             ));
     295           0 :         if (bAllowOSC94)
     296             :         {
     297             :             // Implement OSC 9;4 progress reporting protocol
     298             :             // https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC
     299           0 :             if (nThisTick == MAX_TICKS)
     300           0 :                 fprintf(stdout, "\x1b]9;4;0;100\x07");
     301             :             else
     302           0 :                 fprintf(stdout, "\x1b]9;4;1;%d\x07",
     303           0 :                         (nThisTick * 100) / MAX_TICKS);
     304             :         }
     305             :     }
     306             : 
     307       22485 :     while (nThisTick > nLastTick)
     308             :     {
     309       15626 :         ++nLastTick;
     310       15626 :         if (nLastTick % MAJOR_TICK_SPACING == 0)
     311             :         {
     312        4220 :             const int nPercent = (nLastTick * 100) / MAX_TICKS;
     313        4220 :             nCharacterCountLastTime += fprintf(stdout, "%d", nPercent);
     314             :         }
     315             :         else
     316             :         {
     317       11406 :             nCharacterCountLastTime += fprintf(stdout, ".");
     318             :         }
     319             :     }
     320             : 
     321        6859 :     if (nThisTick == MAX_TICKS)
     322             :     {
     323         380 :         nCharacterCountLastTime += fprintf(stdout, " - done");
     324         380 :         if (nCurTime - nStartTime >= MIN_DELAY_FOR_ETA)
     325             :         {
     326           1 :             const int nElapsed = static_cast<int>(nCurTime - nStartTime);
     327           1 :             const int nHours = nElapsed / 3600;
     328           1 :             const int nMins = (nElapsed % 3600) / 60;
     329           1 :             const int nSecs = nElapsed % 60;
     330           1 :             nCharacterCountLastTime +=
     331           1 :                 fprintf(stdout, " in %02d:%02d:%02d.", nHours, nMins, nSecs);
     332           1 :             for (int i = nCharacterCountLastTime; i < nCharacterCountMax; ++i)
     333           0 :                 nCharacterCountLastTime += fprintf(stdout, " ");
     334             :         }
     335             :         else
     336             :         {
     337         379 :             fprintf(stdout, ".");
     338             :         }
     339         380 :         fprintf(stdout, "\n");
     340             :     }
     341             :     else
     342             :     {
     343        6479 :         if (bETADisplayed)
     344             :         {
     345           0 :             for (int i = nCharacterCountLastTime;
     346           0 :                  i < LENGTH_OF_0_TO_100_PROGRESS; ++i)
     347           0 :                 nCharacterCountLastTime += fprintf(stdout, " ");
     348             : 
     349           0 :             const double dfETA =
     350           0 :                 (nCurTime - nStartTime) * (1.0 / dfComplete - 1);
     351           0 :             const int nETA = static_cast<int>(dfETA + 0.5);
     352           0 :             const int nHours = nETA / 3600;
     353           0 :             const int nMins = (nETA % 3600) / 60;
     354           0 :             const int nSecs = nETA % 60;
     355           0 :             nCharacterCountLastTime +=
     356           0 :                 fprintf(stdout, " - estimated remaining time: %02d:%02d:%02d",
     357             :                         nHours, nMins, nSecs);
     358           0 :             for (int i = nCharacterCountLastTime; i < nCharacterCountMax; ++i)
     359           0 :                 nCharacterCountLastTime += fprintf(stdout, " ");
     360             :         }
     361        6479 :         fflush(stdout);
     362             :     }
     363             : 
     364        6859 :     if (nCharacterCountLastTime > nCharacterCountMax)
     365        6859 :         nCharacterCountMax = nCharacterCountLastTime;
     366             : 
     367        6859 :     return TRUE;
     368             : }

Generated by: LCOV version 1.14