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 : }
|