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