Coverage Report

Created: 2025-06-13 06:29

/src/gdal/port/cpl_progress.cpp
Line
Count
Source (jump to first uncovered line)
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
    if (std::abs(dfMin - dfMax) < 0.0000001)
139
0
        dfMax = dfMin + 0.01;
140
141
0
    psInfo->pData = pData;
142
0
    psInfo->pfnProgress = pfnProgress;
143
0
    psInfo->dfMin = dfMin;
144
0
    psInfo->dfMax = dfMax;
145
146
0
    return static_cast<void *>(psInfo);
147
0
}
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
void CPL_STDCALL GDALDestroyScaledProgress(void *pData)
163
164
0
{
165
0
    CPLFree(pData);
166
0
}
167
168
/************************************************************************/
169
/*                      GDALTermProgressWidth()                         */
170
/************************************************************************/
171
172
static constexpr int GDALTermProgressWidth(int nMaxTicks, int nMajorTickSpacing)
173
0
{
174
0
    int nWidth = 0;
175
0
    for (int i = 0; i <= nMaxTicks; i++)
176
0
    {
177
0
        if (i % nMajorTickSpacing == 0)
178
0
        {
179
0
            int nPercent = (i * 100) / nMaxTicks;
180
0
            do
181
0
            {
182
0
                nWidth++;
183
0
            } while (nPercent /= 10);
184
0
        }
185
0
        else
186
0
        {
187
0
            nWidth += 1;
188
0
        }
189
0
    }
190
0
    return nWidth;
191
0
}
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
int CPL_STDCALL GDALTermProgress(double dfComplete,
232
                                 CPL_UNUSED const char *pszMessage,
233
                                 CPL_UNUSED void *pProgressArg)
234
0
{
235
0
    constexpr int MAX_TICKS = 40;
236
0
    constexpr int MAJOR_TICK_SPACING = 4;
237
0
    constexpr int LENGTH_OF_0_TO_100_PROGRESS =
238
0
        GDALTermProgressWidth(MAX_TICKS, MAJOR_TICK_SPACING);
239
240
0
    const int nThisTick = std::min(
241
0
        MAX_TICKS, std::max(0, static_cast<int>(dfComplete * MAX_TICKS)));
242
243
    // Have we started a new progress run?
244
0
    static int nLastTick = -1;
245
0
    static time_t nStartTime = 0;
246
    // whether estimated remaining time is displayed
247
0
    static bool bETADisplayed = false;
248
    // number of characters displayed during last progress call
249
0
    static int nCharacterCountLastTime = 0;
250
    // maximum number of characters displayed during previous calls
251
0
    static int nCharacterCountMax = 0;
252
0
    if (nThisTick < nLastTick && nLastTick >= MAX_TICKS - 1)
253
0
    {
254
0
        bETADisplayed = false;
255
0
        nLastTick = -1;
256
0
        nCharacterCountLastTime = 0;
257
0
        nCharacterCountMax = 0;
258
0
    }
259
260
0
    if (nThisTick <= nLastTick)
261
0
        return TRUE;
262
263
0
    const time_t nCurTime = time(nullptr);
264
0
    if (nLastTick < 0)
265
0
        nStartTime = nCurTime;
266
267
0
    constexpr int MIN_DELAY_FOR_ETA = 5;  // in seconds
268
0
    if (nCurTime - nStartTime >= MIN_DELAY_FOR_ETA && dfComplete > 0 &&
269
0
        dfComplete < 0.5)
270
0
    {
271
0
        static bool bIsTTY = CPLIsInteractive(stdout);
272
0
        bETADisplayed = bIsTTY;
273
0
    }
274
0
    if (bETADisplayed)
275
0
    {
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
0
            "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
0
            "YES"
296
0
#endif
297
0
            ));
298
0
        if (bAllowOSC94)
299
0
        {
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
0
            else
305
0
                fprintf(stdout, "\x1b]9;4;1;%d\x07",
306
0
                        (nThisTick * 100) / MAX_TICKS);
307
0
        }
308
0
    }
309
310
0
    while (nThisTick > nLastTick)
311
0
    {
312
0
        ++nLastTick;
313
0
        if (nLastTick % MAJOR_TICK_SPACING == 0)
314
0
        {
315
0
            const int nPercent = (nLastTick * 100) / MAX_TICKS;
316
0
            nCharacterCountLastTime += fprintf(stdout, "%d", nPercent);
317
0
        }
318
0
        else
319
0
        {
320
0
            nCharacterCountLastTime += fprintf(stdout, ".");
321
0
        }
322
0
    }
323
324
0
    if (nThisTick == MAX_TICKS)
325
0
    {
326
0
        nCharacterCountLastTime += fprintf(stdout, " - done");
327
0
        if (nCurTime - nStartTime >= MIN_DELAY_FOR_ETA)
328
0
        {
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
0
        }
338
0
        else
339
0
        {
340
0
            fprintf(stdout, ".");
341
0
        }
342
0
        fprintf(stdout, "\n");
343
0
    }
344
0
    else
345
0
    {
346
0
        if (bETADisplayed)
347
0
        {
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
0
                        nHours, nMins, nSecs);
361
0
            for (int i = nCharacterCountLastTime; i < nCharacterCountMax; ++i)
362
0
                nCharacterCountLastTime += fprintf(stdout, " ");
363
0
        }
364
0
        fflush(stdout);
365
0
    }
366
367
0
    if (nCharacterCountLastTime > nCharacterCountMax)
368
0
        nCharacterCountMax = nCharacterCountLastTime;
369
370
0
    return TRUE;
371
0
}