/src/gdal/port/cpl_progress.cpp
Line | Count | Source |
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 | | int CPL_STDCALL GDALDummyProgress(double /* dfComplete */, |
38 | | const char * /* pszMessage */, |
39 | | void * /* pData */) |
40 | 0 | { |
41 | 0 | return TRUE; |
42 | 0 | } |
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 | | int CPL_STDCALL GDALScaledProgress(double dfComplete, const char *pszMessage, |
63 | | void *pData) |
64 | | |
65 | 0 | { |
66 | 0 | GDALScaledProgressInfo *psInfo = |
67 | 0 | reinterpret_cast<GDALScaledProgressInfo *>(pData); |
68 | | |
69 | | // Optimization if GDALCreateScaledProgress() provided with |
70 | | // GDALDummyProgress. |
71 | 0 | if (psInfo == nullptr) |
72 | 0 | return TRUE; |
73 | | |
74 | 0 | return psInfo->pfnProgress(dfComplete * (psInfo->dfMax - psInfo->dfMin) + |
75 | 0 | psInfo->dfMin, |
76 | 0 | pszMessage, psInfo->pData); |
77 | 0 | } |
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 | | void *CPL_STDCALL GDALCreateScaledProgress(double dfMin, double dfMax, |
128 | | GDALProgressFunc pfnProgress, |
129 | | void *pData) |
130 | | |
131 | 0 | { |
132 | 0 | if (pfnProgress == nullptr || pfnProgress == GDALDummyProgress) |
133 | 0 | return nullptr; |
134 | | |
135 | 0 | GDALScaledProgressInfo *psInfo = static_cast<GDALScaledProgressInfo *>( |
136 | 0 | CPLCalloc(sizeof(GDALScaledProgressInfo), 1)); |
137 | |
|
138 | 0 | psInfo->pData = pData; |
139 | 0 | psInfo->pfnProgress = pfnProgress; |
140 | 0 | psInfo->dfMin = dfMin; |
141 | 0 | psInfo->dfMax = dfMax; |
142 | |
|
143 | 0 | return static_cast<void *>(psInfo); |
144 | 0 | } |
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 | | void CPL_STDCALL GDALDestroyScaledProgress(void *pData) |
160 | | |
161 | 0 | { |
162 | 0 | CPLFree(pData); |
163 | 0 | } |
164 | | |
165 | | /************************************************************************/ |
166 | | /* GDALTermProgressWidth() */ |
167 | | /************************************************************************/ |
168 | | |
169 | | static constexpr int GDALTermProgressWidth(int nMaxTicks, int nMajorTickSpacing) |
170 | 0 | { |
171 | 0 | int nWidth = 0; |
172 | 0 | for (int i = 0; i <= nMaxTicks; i++) |
173 | 0 | { |
174 | 0 | if (i % nMajorTickSpacing == 0) |
175 | 0 | { |
176 | 0 | int nPercent = (i * 100) / nMaxTicks; |
177 | 0 | do |
178 | 0 | { |
179 | 0 | nWidth++; |
180 | 0 | } while (nPercent /= 10); |
181 | 0 | } |
182 | 0 | else |
183 | 0 | { |
184 | 0 | nWidth += 1; |
185 | 0 | } |
186 | 0 | } |
187 | 0 | return nWidth; |
188 | 0 | } |
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 | | int CPL_STDCALL GDALTermProgress(double dfComplete, |
229 | | CPL_UNUSED const char *pszMessage, |
230 | | CPL_UNUSED void *pProgressArg) |
231 | 0 | { |
232 | 0 | constexpr int MAX_TICKS = 40; |
233 | 0 | constexpr int MAJOR_TICK_SPACING = 4; |
234 | 0 | constexpr int LENGTH_OF_0_TO_100_PROGRESS = |
235 | 0 | GDALTermProgressWidth(MAX_TICKS, MAJOR_TICK_SPACING); |
236 | |
|
237 | 0 | const int nThisTick = std::min( |
238 | 0 | MAX_TICKS, std::max(0, static_cast<int>(dfComplete * MAX_TICKS))); |
239 | | |
240 | | // Have we started a new progress run? |
241 | 0 | static int nLastTick = -1; |
242 | 0 | static time_t nStartTime = 0; |
243 | | // whether estimated remaining time is displayed |
244 | 0 | static bool bETADisplayed = false; |
245 | | // number of characters displayed during last progress call |
246 | 0 | static int nCharacterCountLastTime = 0; |
247 | | // maximum number of characters displayed during previous calls |
248 | 0 | static int nCharacterCountMax = 0; |
249 | 0 | if (nThisTick < nLastTick && nLastTick >= MAX_TICKS - 1) |
250 | 0 | { |
251 | 0 | bETADisplayed = false; |
252 | 0 | nLastTick = -1; |
253 | 0 | nCharacterCountLastTime = 0; |
254 | 0 | nCharacterCountMax = 0; |
255 | 0 | } |
256 | |
|
257 | 0 | if (nThisTick <= nLastTick) |
258 | 0 | return TRUE; |
259 | | |
260 | 0 | const time_t nCurTime = time(nullptr); |
261 | 0 | if (nLastTick < 0) |
262 | 0 | nStartTime = nCurTime; |
263 | |
|
264 | 0 | constexpr int MIN_DELAY_FOR_ETA = 5; // in seconds |
265 | 0 | if (nCurTime - nStartTime >= MIN_DELAY_FOR_ETA && dfComplete > 0 && |
266 | 0 | dfComplete < 0.5) |
267 | 0 | { |
268 | 0 | static bool bIsTTY = CPLIsInteractive(stdout); |
269 | 0 | bETADisplayed = bIsTTY; |
270 | 0 | } |
271 | 0 | if (bETADisplayed) |
272 | 0 | { |
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 | 0 | "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 | 0 | "YES" |
293 | 0 | #endif |
294 | 0 | )); |
295 | 0 | if (bAllowOSC94) |
296 | 0 | { |
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 | 0 | else |
302 | 0 | fprintf(stdout, "\x1b]9;4;1;%d\x07", |
303 | 0 | (nThisTick * 100) / MAX_TICKS); |
304 | 0 | } |
305 | 0 | } |
306 | |
|
307 | 0 | while (nThisTick > nLastTick) |
308 | 0 | { |
309 | 0 | ++nLastTick; |
310 | 0 | if (nLastTick % MAJOR_TICK_SPACING == 0) |
311 | 0 | { |
312 | 0 | const int nPercent = (nLastTick * 100) / MAX_TICKS; |
313 | 0 | nCharacterCountLastTime += fprintf(stdout, "%d", nPercent); |
314 | 0 | } |
315 | 0 | else |
316 | 0 | { |
317 | 0 | nCharacterCountLastTime += fprintf(stdout, "."); |
318 | 0 | } |
319 | 0 | } |
320 | |
|
321 | 0 | if (nThisTick == MAX_TICKS) |
322 | 0 | { |
323 | 0 | nCharacterCountLastTime += fprintf(stdout, " - done"); |
324 | 0 | if (nCurTime - nStartTime >= MIN_DELAY_FOR_ETA) |
325 | 0 | { |
326 | 0 | const int nElapsed = static_cast<int>(nCurTime - nStartTime); |
327 | 0 | const int nHours = nElapsed / 3600; |
328 | 0 | const int nMins = (nElapsed % 3600) / 60; |
329 | 0 | const int nSecs = nElapsed % 60; |
330 | 0 | nCharacterCountLastTime += |
331 | 0 | fprintf(stdout, " in %02d:%02d:%02d.", nHours, nMins, nSecs); |
332 | 0 | for (int i = nCharacterCountLastTime; i < nCharacterCountMax; ++i) |
333 | 0 | nCharacterCountLastTime += fprintf(stdout, " "); |
334 | 0 | } |
335 | 0 | else |
336 | 0 | { |
337 | 0 | fprintf(stdout, "."); |
338 | 0 | } |
339 | 0 | fprintf(stdout, "\n"); |
340 | 0 | } |
341 | 0 | else |
342 | 0 | { |
343 | 0 | if (bETADisplayed) |
344 | 0 | { |
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 | 0 | nHours, nMins, nSecs); |
358 | 0 | for (int i = nCharacterCountLastTime; i < nCharacterCountMax; ++i) |
359 | 0 | nCharacterCountLastTime += fprintf(stdout, " "); |
360 | 0 | } |
361 | 0 | fflush(stdout); |
362 | 0 | } |
363 | |
|
364 | 0 | if (nCharacterCountLastTime > nCharacterCountMax) |
365 | 0 | nCharacterCountMax = nCharacterCountLastTime; |
366 | |
|
367 | 0 | return TRUE; |
368 | 0 | } |