Coverage Report

Created: 2025-12-31 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}