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 454740 : int CPL_STDCALL GDALDummyProgress(double /* dfComplete */,
38 : const char * /* pszMessage */,
39 : void * /* pData */)
40 : {
41 454740 : 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 833541 : int CPL_STDCALL GDALScaledProgress(double dfComplete, const char *pszMessage,
63 : void *pData)
64 :
65 : {
66 833541 : GDALScaledProgressInfo *psInfo =
67 : reinterpret_cast<GDALScaledProgressInfo *>(pData);
68 :
69 : // Optimization if GDALCreateScaledProgress() provided with
70 : // GDALDummyProgress.
71 833541 : if (psInfo == nullptr)
72 476896 : return TRUE;
73 :
74 713290 : return psInfo->pfnProgress(dfComplete * (psInfo->dfMax - psInfo->dfMin) +
75 356645 : psInfo->dfMin,
76 356645 : 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 509589 : void *CPL_STDCALL GDALCreateScaledProgress(double dfMin, double dfMax,
128 : GDALProgressFunc pfnProgress,
129 : void *pData)
130 :
131 : {
132 509589 : if (pfnProgress == nullptr || pfnProgress == GDALDummyProgress)
133 499326 : return nullptr;
134 :
135 : GDALScaledProgressInfo *psInfo = static_cast<GDALScaledProgressInfo *>(
136 10263 : CPLCalloc(sizeof(GDALScaledProgressInfo), 1));
137 :
138 10263 : if (std::abs(dfMin - dfMax) < 0.0000001)
139 171 : dfMax = dfMin + 0.01;
140 :
141 10263 : psInfo->pData = pData;
142 10263 : psInfo->pfnProgress = pfnProgress;
143 10263 : psInfo->dfMin = dfMin;
144 10263 : psInfo->dfMax = dfMax;
145 :
146 10263 : return static_cast<void *>(psInfo);
147 : }
148 :
149 : /************************************************************************/
150 : /* GDALDestroyScaledProgress() */
151 : /************************************************************************/
152 :
153 : /**
154 : * \brief Cleanup scaled progress handle.
155 : *
156 : * This function cleans up the data associated with a scaled progress function
157 : * as returned by GADLCreateScaledProgress().
158 : *
159 : * @param pData scaled progress handle returned by GDALCreateScaledProgress().
160 : */
161 :
162 509515 : void CPL_STDCALL GDALDestroyScaledProgress(void *pData)
163 :
164 : {
165 509515 : CPLFree(pData);
166 509510 : }
167 :
168 : /************************************************************************/
169 : /* GDALTermProgressWidth() */
170 : /************************************************************************/
171 :
172 : static constexpr int GDALTermProgressWidth(int nMaxTicks, int nMajorTickSpacing)
173 : {
174 : int nWidth = 0;
175 : for (int i = 0; i <= nMaxTicks; i++)
176 : {
177 : if (i % nMajorTickSpacing == 0)
178 : {
179 : int nPercent = (i * 100) / nMaxTicks;
180 : do
181 : {
182 : nWidth++;
183 : } while (nPercent /= 10);
184 : }
185 : else
186 : {
187 : nWidth += 1;
188 : }
189 : }
190 : return nWidth;
191 : }
192 :
193 : /************************************************************************/
194 : /* GDALTermProgress() */
195 : /************************************************************************/
196 :
197 : /**
198 : * \fn GDALTermProgress(double, const char*, void*)
199 : * \brief Simple progress report to terminal.
200 : *
201 : * This progress reporter prints simple progress report to the
202 : * terminal window. The progress report generally looks something like
203 : * this:
204 :
205 : \verbatim
206 : 0...10...20...30...40...50...60...70...80...90...100 - done.
207 : \endverbatim
208 :
209 : * Starting with GDAL 3.11, for tasks estimated to take more than 10 seconds,
210 : * an estimated remaining time is also displayed at the end. And for tasks
211 : * taking more than 5 seconds to complete, the total time is displayed upon
212 : * completion.
213 : *
214 : * Every 2.5% of progress another number or period is emitted. Note that
215 : * GDALTermProgress() uses internal static data to keep track of the last
216 : * percentage reported and will get confused if two terminal based progress
217 : * reportings are active at the same time.
218 : *
219 : * The GDALTermProgress() function maintains an internal memory of the
220 : * last percentage complete reported in a static variable, and this makes
221 : * it unsuitable to have multiple GDALTermProgress()'s active either in a
222 : * single thread or across multiple threads.
223 : *
224 : * @param dfComplete completion ratio from 0.0 to 1.0.
225 : * @param pszMessage optional message.
226 : * @param pProgressArg ignored callback data argument.
227 : *
228 : * @return Always returns TRUE indicating the process should continue.
229 : */
230 :
231 112111 : int CPL_STDCALL GDALTermProgress(double dfComplete,
232 : CPL_UNUSED const char *pszMessage,
233 : CPL_UNUSED void *pProgressArg)
234 : {
235 112111 : constexpr int MAX_TICKS = 40;
236 112111 : constexpr int MAJOR_TICK_SPACING = 4;
237 112111 : constexpr int LENGTH_OF_0_TO_100_PROGRESS =
238 : GDALTermProgressWidth(MAX_TICKS, MAJOR_TICK_SPACING);
239 :
240 : const int nThisTick = std::min(
241 112111 : MAX_TICKS, std::max(0, static_cast<int>(dfComplete * MAX_TICKS)));
242 :
243 : // Have we started a new progress run?
244 : static int nLastTick = -1;
245 : static time_t nStartTime = 0;
246 : // whether estimated remaining time is displayed
247 : static bool bETADisplayed = false;
248 : // number of characters displayed during last progress call
249 : static int nCharacterCountLastTime = 0;
250 : // maximum number of characters displayed during previous calls
251 : static int nCharacterCountMax = 0;
252 112111 : if (nThisTick < nLastTick && nLastTick >= MAX_TICKS - 1)
253 : {
254 9 : bETADisplayed = false;
255 9 : nLastTick = -1;
256 9 : nCharacterCountLastTime = 0;
257 9 : nCharacterCountMax = 0;
258 : }
259 :
260 112111 : if (nThisTick <= nLastTick)
261 104740 : return TRUE;
262 :
263 7371 : const time_t nCurTime = time(nullptr);
264 7371 : if (nLastTick < 0)
265 436 : nStartTime = nCurTime;
266 :
267 7371 : constexpr int MIN_DELAY_FOR_ETA = 5; // in seconds
268 7371 : if (nCurTime - nStartTime >= MIN_DELAY_FOR_ETA && dfComplete > 0 &&
269 : dfComplete < 0.5)
270 : {
271 0 : static bool bIsTTY = CPLIsInteractive(stdout);
272 0 : bETADisplayed = bIsTTY;
273 : }
274 7371 : if (bETADisplayed)
275 : {
276 0 : for (int i = 0; i < nCharacterCountLastTime; ++i)
277 0 : fprintf(stdout, "\b");
278 0 : nLastTick = -1;
279 0 : nCharacterCountLastTime = 0;
280 :
281 : #ifdef _WIN32
282 : constexpr const char *WINDOWS_TERMINAL_ENV_VAR = "WT_SESSION";
283 : constexpr const char *CONEMU_ENV_VAR = "ConEmuANSI";
284 : #endif
285 0 : static const bool bAllowOSC94 = CPLTestBool(CPLGetConfigOption(
286 : "GDAL_TERM_PROGRESS_OSC_9_4",
287 : #ifdef _WIN32
288 : // Detect if we are running under Windows Terminal
289 : (CPLGetConfigOption(WINDOWS_TERMINAL_ENV_VAR, nullptr) != nullptr ||
290 : // or ConEmu
291 : CPLGetConfigOption(CONEMU_ENV_VAR, nullptr) != nullptr)
292 : ? "YES"
293 : : "NO"
294 : #else
295 : "YES"
296 : #endif
297 0 : ));
298 0 : if (bAllowOSC94)
299 : {
300 : // Implement OSC 9;4 progress reporting protocol
301 : // https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC
302 0 : if (nThisTick == MAX_TICKS)
303 0 : fprintf(stdout, "\x1b]9;4;0;100\x07");
304 : else
305 0 : fprintf(stdout, "\x1b]9;4;1;%d\x07",
306 0 : (nThisTick * 100) / MAX_TICKS);
307 : }
308 : }
309 :
310 23255 : while (nThisTick > nLastTick)
311 : {
312 15884 : ++nLastTick;
313 15884 : if (nLastTick % MAJOR_TICK_SPACING == 0)
314 : {
315 4298 : const int nPercent = (nLastTick * 100) / MAX_TICKS;
316 4298 : nCharacterCountLastTime += fprintf(stdout, "%d", nPercent);
317 : }
318 : else
319 : {
320 11586 : nCharacterCountLastTime += fprintf(stdout, ".");
321 : }
322 : }
323 :
324 7371 : if (nThisTick == MAX_TICKS)
325 : {
326 386 : nCharacterCountLastTime += fprintf(stdout, " - done");
327 386 : if (nCurTime - nStartTime >= MIN_DELAY_FOR_ETA)
328 : {
329 0 : const int nElapsed = static_cast<int>(nCurTime - nStartTime);
330 0 : const int nHours = nElapsed / 3600;
331 0 : const int nMins = (nElapsed % 3600) / 60;
332 0 : const int nSecs = nElapsed % 60;
333 0 : nCharacterCountLastTime +=
334 0 : fprintf(stdout, " in %02d:%02d:%02d.", nHours, nMins, nSecs);
335 0 : for (int i = nCharacterCountLastTime; i < nCharacterCountMax; ++i)
336 0 : nCharacterCountLastTime += fprintf(stdout, " ");
337 : }
338 : else
339 : {
340 386 : fprintf(stdout, ".");
341 : }
342 386 : fprintf(stdout, "\n");
343 : }
344 : else
345 : {
346 6985 : if (bETADisplayed)
347 : {
348 0 : for (int i = nCharacterCountLastTime;
349 0 : i < LENGTH_OF_0_TO_100_PROGRESS; ++i)
350 0 : nCharacterCountLastTime += fprintf(stdout, " ");
351 :
352 0 : const double dfETA =
353 0 : (nCurTime - nStartTime) * (1.0 / dfComplete - 1);
354 0 : const int nETA = static_cast<int>(dfETA + 0.5);
355 0 : const int nHours = nETA / 3600;
356 0 : const int nMins = (nETA % 3600) / 60;
357 0 : const int nSecs = nETA % 60;
358 0 : nCharacterCountLastTime +=
359 0 : fprintf(stdout, " - estimated remaining time: %02d:%02d:%02d",
360 : nHours, nMins, nSecs);
361 0 : for (int i = nCharacterCountLastTime; i < nCharacterCountMax; ++i)
362 0 : nCharacterCountLastTime += fprintf(stdout, " ");
363 : }
364 6985 : fflush(stdout);
365 : }
366 :
367 7371 : if (nCharacterCountLastTime > nCharacterCountMax)
368 7371 : nCharacterCountMax = nCharacterCountLastTime;
369 :
370 7371 : return TRUE;
371 : }
|