/src/gdal/frmts/eeda/eedacommon.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: Earth Engine Data API Images driver |
4 | | * Purpose: Earth Engine Data API Images driver |
5 | | * Author: Even Rouault, even dot rouault at spatialys.com |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2017-2018, Planet Labs |
9 | | * |
10 | | * SPDX-License-Identifier: MIT |
11 | | ****************************************************************************/ |
12 | | |
13 | | #include "cpl_http.h" |
14 | | #include "cpl_multiproc.h" // CPLSleep |
15 | | #include "eeda.h" |
16 | | #include "ogrlibjsonutils.h" |
17 | | |
18 | | #include <stdlib.h> |
19 | | #include <limits> |
20 | | |
21 | | std::vector<EEDAIBandDesc> |
22 | | BuildBandDescArray(json_object *poBands, |
23 | | std::map<CPLString, CPLString> &oMapCodeToWKT) |
24 | 0 | { |
25 | 0 | const auto nBandCount = json_object_array_length(poBands); |
26 | 0 | std::vector<EEDAIBandDesc> aoBandDesc; |
27 | |
|
28 | 0 | for (auto i = decltype(nBandCount){0}; i < nBandCount; i++) |
29 | 0 | { |
30 | 0 | json_object *poBand = json_object_array_get_idx(poBands, i); |
31 | 0 | if (poBand == nullptr || |
32 | 0 | json_object_get_type(poBand) != json_type_object) |
33 | 0 | continue; |
34 | | |
35 | 0 | json_object *poId = CPL_json_object_object_get(poBand, "id"); |
36 | 0 | const char *pszBandId = json_object_get_string(poId); |
37 | 0 | if (pszBandId == nullptr) |
38 | 0 | continue; |
39 | | |
40 | 0 | json_object *poDataType = |
41 | 0 | CPL_json_object_object_get(poBand, "dataType"); |
42 | 0 | if (poDataType == nullptr || |
43 | 0 | json_object_get_type(poDataType) != json_type_object) |
44 | 0 | { |
45 | 0 | continue; |
46 | 0 | } |
47 | | |
48 | 0 | json_object *poPrecision = |
49 | 0 | CPL_json_object_object_get(poDataType, "precision"); |
50 | 0 | const char *pszPrecision = json_object_get_string(poPrecision); |
51 | 0 | if (pszPrecision == nullptr) |
52 | 0 | continue; |
53 | 0 | GDALDataType eDT = GDT_UInt8; |
54 | 0 | if (EQUAL(pszPrecision, "INT")) |
55 | 0 | { |
56 | 0 | json_object *poRange = |
57 | 0 | CPL_json_object_object_get(poDataType, "range"); |
58 | 0 | if (poRange && json_object_get_type(poRange) == json_type_object) |
59 | 0 | { |
60 | 0 | int nMin = 0; |
61 | 0 | int nMax = 0; |
62 | 0 | json_object *poMin = CPL_json_object_object_get(poRange, "min"); |
63 | 0 | if (poMin) |
64 | 0 | { |
65 | 0 | nMin = json_object_get_int(poMin); |
66 | 0 | } |
67 | 0 | json_object *poMax = CPL_json_object_object_get(poRange, "max"); |
68 | 0 | if (poMax) |
69 | 0 | { |
70 | 0 | nMax = json_object_get_int(poMax); |
71 | 0 | } |
72 | |
|
73 | 0 | if (nMin == -128 && nMax == 127) |
74 | 0 | { |
75 | 0 | eDT = GDT_Int8; |
76 | 0 | } |
77 | 0 | else if (nMin < std::numeric_limits<GInt16>::min()) |
78 | 0 | { |
79 | 0 | eDT = GDT_Int32; |
80 | 0 | } |
81 | 0 | else if (nMax > std::numeric_limits<GUInt16>::max()) |
82 | 0 | { |
83 | 0 | eDT = GDT_UInt32; |
84 | 0 | } |
85 | 0 | else if (nMin < 0) |
86 | 0 | { |
87 | 0 | eDT = GDT_Int16; |
88 | 0 | } |
89 | 0 | else if (nMax > std::numeric_limits<GByte>::max()) |
90 | 0 | { |
91 | 0 | eDT = GDT_UInt16; |
92 | 0 | } |
93 | 0 | } |
94 | 0 | } |
95 | 0 | else if (EQUAL(pszPrecision, "FLOAT")) |
96 | 0 | { |
97 | 0 | eDT = GDT_Float32; |
98 | 0 | } |
99 | 0 | else if (EQUAL(pszPrecision, "DOUBLE")) |
100 | 0 | { |
101 | 0 | eDT = GDT_Float64; |
102 | 0 | } |
103 | 0 | else |
104 | 0 | { |
105 | 0 | CPLError(CE_Warning, CPLE_NotSupported, |
106 | 0 | "Unhandled dataType %s for band %s", pszPrecision, |
107 | 0 | pszBandId); |
108 | 0 | continue; |
109 | 0 | } |
110 | | |
111 | 0 | json_object *poGrid = CPL_json_object_object_get(poBand, "grid"); |
112 | 0 | if (poGrid == nullptr || |
113 | 0 | json_object_get_type(poGrid) != json_type_object) |
114 | 0 | { |
115 | 0 | continue; |
116 | 0 | } |
117 | | |
118 | 0 | CPLString osWKT; |
119 | | // Cf https://developers.google.com/earth-engine/reference/rest/v1alpha/PixelGrid |
120 | 0 | json_object *poCrs = CPL_json_object_object_get(poGrid, "crsCode"); |
121 | 0 | if (poCrs == nullptr) |
122 | 0 | poCrs = CPL_json_object_object_get(poGrid, "crsWkt"); |
123 | 0 | if (poCrs == |
124 | 0 | nullptr) // "wkt" must come from a preliminary version of the API |
125 | 0 | poCrs = CPL_json_object_object_get(poGrid, "wkt"); |
126 | 0 | OGRSpatialReference oSRS; |
127 | 0 | if (poCrs) |
128 | 0 | { |
129 | 0 | const char *pszStr = json_object_get_string(poCrs); |
130 | 0 | if (pszStr == nullptr) |
131 | 0 | continue; |
132 | 0 | if (STARTS_WITH(pszStr, "SR-ORG:")) |
133 | 0 | { |
134 | | // For EEDA:MCD12Q1 for example |
135 | 0 | pszStr = |
136 | 0 | CPLSPrintf("http://spatialreference.org/ref/sr-org/%s/", |
137 | 0 | pszStr + strlen("SR-ORG:")); |
138 | 0 | } |
139 | |
|
140 | 0 | std::map<CPLString, CPLString>::const_iterator oIter = |
141 | 0 | oMapCodeToWKT.find(pszStr); |
142 | 0 | if (oIter != oMapCodeToWKT.end()) |
143 | 0 | { |
144 | 0 | osWKT = oIter->second; |
145 | 0 | } |
146 | 0 | else if (oSRS.SetFromUserInput(pszStr) != OGRERR_NONE) |
147 | 0 | { |
148 | 0 | CPLError(CE_Warning, CPLE_AppDefined, "Unrecognized crs: %s", |
149 | 0 | pszStr); |
150 | 0 | oMapCodeToWKT[pszStr] = ""; |
151 | 0 | } |
152 | 0 | else |
153 | 0 | { |
154 | 0 | char *pszWKT = nullptr; |
155 | 0 | oSRS.exportToWkt(&pszWKT); |
156 | 0 | if (pszWKT != nullptr) |
157 | 0 | osWKT = pszWKT; |
158 | 0 | CPLFree(pszWKT); |
159 | 0 | oMapCodeToWKT[pszStr] = osWKT; |
160 | 0 | } |
161 | 0 | } |
162 | | |
163 | 0 | json_object *poAT = |
164 | 0 | CPL_json_object_object_get(poGrid, "affineTransform"); |
165 | 0 | if (poAT == nullptr || json_object_get_type(poAT) != json_type_object) |
166 | 0 | { |
167 | 0 | continue; |
168 | 0 | } |
169 | 0 | GDALGeoTransform gt{ |
170 | 0 | json_object_get_double( |
171 | 0 | CPL_json_object_object_get(poAT, "translateX")), |
172 | 0 | json_object_get_double(CPL_json_object_object_get(poAT, "scaleX")), |
173 | 0 | json_object_get_double(CPL_json_object_object_get(poAT, "shearX")), |
174 | 0 | json_object_get_double( |
175 | 0 | CPL_json_object_object_get(poAT, "translateY")), |
176 | 0 | json_object_get_double(CPL_json_object_object_get(poAT, "shearY")), |
177 | 0 | json_object_get_double(CPL_json_object_object_get(poAT, "scaleY")), |
178 | 0 | }; |
179 | |
|
180 | 0 | json_object *poDimensions = |
181 | 0 | CPL_json_object_object_get(poGrid, "dimensions"); |
182 | 0 | if (poDimensions == nullptr || |
183 | 0 | json_object_get_type(poDimensions) != json_type_object) |
184 | 0 | { |
185 | 0 | continue; |
186 | 0 | } |
187 | 0 | json_object *poWidth = |
188 | 0 | CPL_json_object_object_get(poDimensions, "width"); |
189 | 0 | int nWidth = json_object_get_int(poWidth); |
190 | 0 | json_object *poHeight = |
191 | 0 | CPL_json_object_object_get(poDimensions, "height"); |
192 | 0 | int nHeight = json_object_get_int(poHeight); |
193 | |
|
194 | | #if 0 |
195 | | if( poWidth == nullptr && poHeight == nullptr && poX == nullptr && poY == nullptr && |
196 | | dfResX == 1.0 && dfResY == 1.0 ) |
197 | | { |
198 | | // e.g. EEDAI:LT5_L1T_8DAY_EVI/19840109 |
199 | | const char* pszAuthorityName = oSRS.GetAuthorityName(); |
200 | | const char* pszAuthorityCode = oSRS.GetAuthorityCode(); |
201 | | if( pszAuthorityName && pszAuthorityCode && |
202 | | EQUAL(pszAuthorityName, "EPSG") && |
203 | | EQUAL(pszAuthorityCode, "4326") ) |
204 | | { |
205 | | dfX = -180; |
206 | | dfY = 90; |
207 | | nWidth = 1 << 30; |
208 | | nHeight = 1 << 29; |
209 | | dfResX = 360.0 / nWidth; |
210 | | dfResY = -dfResX; |
211 | | } |
212 | | } |
213 | | #endif |
214 | |
|
215 | 0 | if (nWidth <= 0 || nHeight <= 0) |
216 | 0 | { |
217 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
218 | 0 | "Invalid width/height for band %s", pszBandId); |
219 | 0 | continue; |
220 | 0 | } |
221 | | |
222 | 0 | EEDAIBandDesc oDesc; |
223 | 0 | oDesc.osName = pszBandId; |
224 | 0 | oDesc.osWKT = std::move(osWKT); |
225 | 0 | oDesc.eDT = eDT; |
226 | 0 | oDesc.gt = std::move(gt); |
227 | 0 | oDesc.nWidth = nWidth; |
228 | 0 | oDesc.nHeight = nHeight; |
229 | 0 | aoBandDesc.emplace_back(std::move(oDesc)); |
230 | 0 | } |
231 | 0 | return aoBandDesc; |
232 | 0 | } |
233 | | |
234 | | /************************************************************************/ |
235 | | /* GDALEEDABaseDataset() */ |
236 | | /************************************************************************/ |
237 | | |
238 | | GDALEEDABaseDataset::GDALEEDABaseDataset() |
239 | 81 | : m_bMustCleanPersistent(false), m_nExpirationTime(0) |
240 | 81 | { |
241 | 81 | } |
242 | | |
243 | | /************************************************************************/ |
244 | | /* ~GDALEEDABaseDataset() */ |
245 | | /************************************************************************/ |
246 | | |
247 | | GDALEEDABaseDataset::~GDALEEDABaseDataset() |
248 | 81 | { |
249 | 81 | if (m_bMustCleanPersistent) |
250 | 81 | { |
251 | 81 | char **papszOptions = CSLSetNameValue(nullptr, "CLOSE_PERSISTENT", |
252 | 81 | CPLSPrintf("EEDAI:%p", this)); |
253 | 81 | CPLHTTPDestroyResult(CPLHTTPFetch(m_osBaseURL, papszOptions)); |
254 | 81 | CSLDestroy(papszOptions); |
255 | 81 | } |
256 | 81 | } |
257 | | |
258 | | /************************************************************************/ |
259 | | /* ConvertPathToName() */ |
260 | | /************************************************************************/ |
261 | | |
262 | | CPLString GDALEEDABaseDataset::ConvertPathToName(const CPLString &path) |
263 | 81 | { |
264 | 81 | size_t end = path.find('/'); |
265 | 81 | CPLString folder = path.substr(0, end); |
266 | | |
267 | 81 | if (folder == "users") |
268 | 0 | { |
269 | 0 | return "projects/earthengine-legacy/assets/" + path; |
270 | 0 | } |
271 | 81 | else if (folder != "projects") |
272 | 81 | { |
273 | 81 | return "projects/earthengine-public/assets/" + path; |
274 | 81 | } |
275 | | |
276 | | // Find the start and end positions of the third segment, if it exists. |
277 | 0 | int segment = 1; |
278 | 0 | size_t start = 0; |
279 | 0 | while (end != std::string::npos && segment < 3) |
280 | 0 | { |
281 | 0 | segment++; |
282 | 0 | start = end + 1; |
283 | 0 | end = path.find('/', start); |
284 | 0 | } |
285 | |
|
286 | 0 | end = (end == std::string::npos) ? path.size() : end; |
287 | | // segment is 3 if path has at least 3 segments. |
288 | 0 | if (folder == "projects" && segment == 3) |
289 | 0 | { |
290 | | // If the first segment is "projects" and the third segment is "assets", |
291 | | // path is a name, so return as-is. |
292 | 0 | if (path.substr(start, end - start) == "assets") |
293 | 0 | { |
294 | 0 | return path; |
295 | 0 | } |
296 | 0 | } |
297 | 0 | return "projects/earthengine-legacy/assets/" + path; |
298 | 0 | } |
299 | | |
300 | | /************************************************************************/ |
301 | | /* GetBaseHTTPOptions() */ |
302 | | /************************************************************************/ |
303 | | |
304 | | char **GDALEEDABaseDataset::GetBaseHTTPOptions() |
305 | 81 | { |
306 | 81 | m_bMustCleanPersistent = true; |
307 | | |
308 | 81 | char **papszOptions = nullptr; |
309 | 81 | papszOptions = |
310 | 81 | CSLAddString(papszOptions, CPLSPrintf("PERSISTENT=EEDAI:%p", this)); |
311 | | |
312 | | // Strategy to get the Bearer Authorization value: |
313 | | // - if it is specified in the EEDA_BEARER config option, use it |
314 | | // - otherwise if EEDA_BEARER_FILE is specified, read it and use its content |
315 | | // - otherwise if GOOGLE_APPLICATION_CREDENTIALS is specified, read the |
316 | | // corresponding file to get the private key and client_email, to get a |
317 | | // bearer using OAuth2ServiceAccount method |
318 | | // - otherwise if EEDA_PRIVATE_KEY and EEDA_CLIENT_EMAIL are set, use them |
319 | | // to get a bearer using OAuth2ServiceAccount method |
320 | | // - otherwise if EEDA_PRIVATE_KEY_FILE and EEDA_CLIENT_EMAIL are set, use |
321 | | // them to get a bearer |
322 | | |
323 | 81 | CPLString osBearer(CPLGetConfigOption("EEDA_BEARER", m_osBearer)); |
324 | 81 | if (osBearer.empty() || |
325 | 0 | (!m_osBearer.empty() && time(nullptr) > m_nExpirationTime)) |
326 | 81 | { |
327 | 81 | CPLString osBearerFile(CPLGetConfigOption("EEDA_BEARER_FILE", "")); |
328 | 81 | if (!osBearerFile.empty()) |
329 | 0 | { |
330 | 0 | VSILFILE *fp = VSIFOpenL(osBearerFile, "rb"); |
331 | 0 | if (fp == nullptr) |
332 | 0 | { |
333 | 0 | CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s", |
334 | 0 | osBearerFile.c_str()); |
335 | 0 | } |
336 | 0 | else |
337 | 0 | { |
338 | 0 | char abyBuffer[512]; |
339 | 0 | size_t nRead = VSIFReadL(abyBuffer, 1, sizeof(abyBuffer), fp); |
340 | 0 | osBearer.assign(abyBuffer, nRead); |
341 | 0 | VSIFCloseL(fp); |
342 | 0 | } |
343 | 0 | } |
344 | 81 | else |
345 | 81 | { |
346 | 81 | CPLString osPrivateKey(CPLGetConfigOption("EEDA_PRIVATE_KEY", "")); |
347 | 81 | CPLString osClientEmail( |
348 | 81 | CPLGetConfigOption("EEDA_CLIENT_EMAIL", "")); |
349 | | |
350 | 81 | if (osPrivateKey.empty()) |
351 | 81 | { |
352 | 81 | CPLString osPrivateKeyFile( |
353 | 81 | CPLGetConfigOption("EEDA_PRIVATE_KEY_FILE", "")); |
354 | 81 | if (!osPrivateKeyFile.empty()) |
355 | 0 | { |
356 | 0 | VSILFILE *fp = VSIFOpenL(osPrivateKeyFile, "rb"); |
357 | 0 | if (fp == nullptr) |
358 | 0 | { |
359 | 0 | CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s", |
360 | 0 | osPrivateKeyFile.c_str()); |
361 | 0 | } |
362 | 0 | else |
363 | 0 | { |
364 | 0 | char *pabyBuffer = |
365 | 0 | static_cast<char *>(CPLMalloc(32768)); |
366 | 0 | size_t nRead = VSIFReadL(pabyBuffer, 1, 32768, fp); |
367 | 0 | osPrivateKey.assign(pabyBuffer, nRead); |
368 | 0 | VSIFCloseL(fp); |
369 | 0 | CPLFree(pabyBuffer); |
370 | 0 | } |
371 | 0 | } |
372 | 81 | } |
373 | | |
374 | 81 | CPLString osServiceAccountJson; |
375 | 81 | const char *pszVSIPath = |
376 | 81 | CSLFetchNameValue(papszOpenOptions, "VSI_PATH_FOR_AUTH"); |
377 | 81 | if (pszVSIPath) |
378 | 0 | osServiceAccountJson = VSIGetPathSpecificOption( |
379 | 0 | pszVSIPath, "GOOGLE_APPLICATION_CREDENTIALS", ""); |
380 | 81 | if (osServiceAccountJson.empty()) |
381 | 81 | osServiceAccountJson = |
382 | 81 | CPLGetConfigOption("GOOGLE_APPLICATION_CREDENTIALS", ""); |
383 | 81 | if (!osServiceAccountJson.empty()) |
384 | 0 | { |
385 | 0 | CPLJSONDocument oDoc; |
386 | 0 | if (!oDoc.Load(osServiceAccountJson)) |
387 | 0 | { |
388 | 0 | CSLDestroy(papszOptions); |
389 | 0 | return nullptr; |
390 | 0 | } |
391 | | |
392 | 0 | osPrivateKey = oDoc.GetRoot().GetString("private_key"); |
393 | 0 | osPrivateKey.replaceAll("\\n", "\n"); |
394 | 0 | osClientEmail = oDoc.GetRoot().GetString("client_email"); |
395 | 0 | } |
396 | | |
397 | 81 | char **papszMD = nullptr; |
398 | 81 | if (!osPrivateKey.empty() && !osClientEmail.empty()) |
399 | 0 | { |
400 | 0 | CPLDebug("EEDA", "Requesting Bearer token"); |
401 | 0 | osPrivateKey.replaceAll("\\n", "\n"); |
402 | | // CPLDebug("EEDA", "Private key: %s", osPrivateKey.c_str()); |
403 | 0 | papszMD = GOA2GetAccessTokenFromServiceAccount( |
404 | 0 | osPrivateKey, osClientEmail, |
405 | 0 | "https://www.googleapis.com/auth/earthengine.readonly", |
406 | 0 | nullptr, nullptr); |
407 | 0 | if (papszMD == nullptr) |
408 | 0 | { |
409 | 0 | CSLDestroy(papszOptions); |
410 | 0 | return nullptr; |
411 | 0 | } |
412 | 0 | } |
413 | | // Some Travis-CI workers are GCE machines, and for some tests, we |
414 | | // don't want this code path to be taken. And on AppVeyor/Window, we |
415 | | // would also attempt a network access |
416 | 81 | else if (!CPLTestBool(CPLGetConfigOption("CPL_GCE_SKIP", "NO")) && |
417 | 81 | CPLIsMachinePotentiallyGCEInstance()) |
418 | 81 | { |
419 | 81 | papszMD = GOA2GetAccessTokenFromCloudEngineVM(nullptr); |
420 | 81 | } |
421 | | |
422 | 81 | if (papszMD) |
423 | 81 | { |
424 | 81 | osBearer = CSLFetchNameValueDef(papszMD, "access_token", ""); |
425 | 81 | m_osBearer = osBearer; |
426 | 81 | m_nExpirationTime = CPLAtoGIntBig( |
427 | 81 | CSLFetchNameValueDef(papszMD, "expires_in", "0")); |
428 | 81 | if (m_nExpirationTime != 0) |
429 | 81 | m_nExpirationTime += time(nullptr) - 10; |
430 | 81 | CSLDestroy(papszMD); |
431 | 81 | } |
432 | 0 | else |
433 | 0 | { |
434 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
435 | 0 | "Missing EEDA_BEARER, EEDA_BEARER_FILE or " |
436 | 0 | "GOOGLE_APPLICATION_CREDENTIALS or " |
437 | 0 | "EEDA_PRIVATE_KEY/EEDA_PRIVATE_KEY_FILE + " |
438 | 0 | "EEDA_CLIENT_EMAIL config option or " |
439 | 0 | "VSI_PATH_FOR_AUTH open option"); |
440 | 0 | CSLDestroy(papszOptions); |
441 | 0 | return nullptr; |
442 | 0 | } |
443 | 81 | } |
444 | 81 | } |
445 | 81 | papszOptions = CSLAddString( |
446 | 81 | papszOptions, |
447 | 81 | CPLSPrintf("HEADERS=Authorization: Bearer %s", osBearer.c_str())); |
448 | | |
449 | 81 | return papszOptions; |
450 | 81 | } |
451 | | |
452 | | /* Add a small amount of random jitter to avoid cyclic server stampedes */ |
453 | | static double EEDABackoffFactor(double base) |
454 | 0 | { |
455 | | // We don't need cryptographic quality randomness... |
456 | 0 | return base |
457 | 0 | #ifndef __COVERITY__ |
458 | 0 | + rand() * 0.5 / RAND_MAX |
459 | 0 | #endif |
460 | 0 | ; |
461 | 0 | } |
462 | | |
463 | | /************************************************************************/ |
464 | | /* EEDAHTTPFetch() */ |
465 | | /************************************************************************/ |
466 | | |
467 | | CPLHTTPResult *EEDAHTTPFetch(const char *pszURL, CSLConstList papszOptions) |
468 | 81 | { |
469 | 81 | CPLHTTPResult *psResult; |
470 | 81 | const int RETRY_COUNT = 4; |
471 | 81 | double dfRetryDelay = 1.0; |
472 | 81 | for (int i = 0; i <= RETRY_COUNT; i++) |
473 | 81 | { |
474 | 81 | psResult = CPLHTTPFetch(pszURL, papszOptions); |
475 | | |
476 | 81 | if (psResult == nullptr) |
477 | 0 | break; |
478 | 81 | if (psResult->nDataLen != 0 && psResult->nStatus == 0 && |
479 | 70 | psResult->pszErrBuf == nullptr) |
480 | 0 | { |
481 | | /* got a valid response */ |
482 | 0 | CPLErrorReset(); |
483 | 0 | break; |
484 | 0 | } |
485 | 81 | else |
486 | 81 | { |
487 | 81 | const char *pszErrorText = |
488 | 81 | psResult->pszErrBuf ? psResult->pszErrBuf : "(null)"; |
489 | | |
490 | | /* Get HTTP status code */ |
491 | 81 | int nHTTPStatus = -1; |
492 | 81 | if (psResult->pszErrBuf != nullptr && |
493 | 81 | EQUALN(psResult->pszErrBuf, |
494 | 81 | "HTTP error code : ", strlen("HTTP error code : "))) |
495 | 70 | { |
496 | 70 | nHTTPStatus = |
497 | 70 | atoi(psResult->pszErrBuf + strlen("HTTP error code : ")); |
498 | 70 | if (psResult->pabyData) |
499 | 70 | pszErrorText = |
500 | 70 | reinterpret_cast<const char *>(psResult->pabyData); |
501 | 70 | } |
502 | | |
503 | 81 | if ((nHTTPStatus == 429 || nHTTPStatus == 500 || |
504 | 81 | (nHTTPStatus >= 502 && nHTTPStatus <= 504)) && |
505 | 0 | i < RETRY_COUNT) |
506 | 0 | { |
507 | 0 | CPLError(CE_Warning, CPLE_FileIO, |
508 | 0 | "GET error when downloading %s, HTTP status=%d, " |
509 | 0 | "retrying in %.2fs : %s", |
510 | 0 | pszURL, nHTTPStatus, dfRetryDelay, pszErrorText); |
511 | 0 | CPLHTTPDestroyResult(psResult); |
512 | 0 | psResult = nullptr; |
513 | |
|
514 | 0 | CPLSleep(dfRetryDelay); |
515 | 0 | dfRetryDelay *= EEDABackoffFactor(4); |
516 | 0 | } |
517 | 81 | else |
518 | 81 | { |
519 | 81 | break; |
520 | 81 | } |
521 | 81 | } |
522 | 81 | } |
523 | | |
524 | 81 | return psResult; |
525 | 81 | } |