Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: Zarr driver. Virtual file system for
5 : * https://fsspec.github.io/kerchunk/spec.html#version-1
6 : * Author: Even Rouault <even dot rouault at spatialys.com>
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #undef _REENTRANT
15 :
16 : #include "vsikerchunk.h"
17 : #include "vsikerchunk_inline.hpp"
18 :
19 : #include "cpl_conv.h"
20 : #include "cpl_json.h"
21 : #include "cpl_json_streaming_parser.h"
22 : #include "cpl_json_streaming_writer.h"
23 : #include "cpl_mem_cache.h"
24 : #include "cpl_multiproc.h" // CPLSleep()
25 : #include "cpl_vsi_error.h"
26 : #include "cpl_vsi_virtual.h"
27 :
28 : #include "gdal_priv.h"
29 : #include "ogrsf_frmts.h"
30 :
31 : #include <cerrno>
32 : #include <cinttypes>
33 : #include <limits>
34 : #include <mutex>
35 : #include <set>
36 : #include <utility>
37 :
38 : /************************************************************************/
39 : /* VSIKerchunkKeyInfo */
40 : /************************************************************************/
41 :
42 : struct VSIKerchunkKeyInfo
43 : {
44 : // points to an element in VSIKerchunkRefFile::m_oSetURI
45 : const std::string *posURI = nullptr;
46 :
47 : uint64_t nOffset = 0;
48 : uint32_t nSize = 0;
49 : std::vector<GByte> abyValue{};
50 : };
51 :
52 : /************************************************************************/
53 : /* VSIKerchunkRefFile */
54 : /************************************************************************/
55 :
56 : class VSIKerchunkRefFile
57 : {
58 : private:
59 : std::set<std::string> m_oSetURI{};
60 : std::map<std::string, VSIKerchunkKeyInfo> m_oMapKeys{};
61 :
62 : public:
63 646 : const std::map<std::string, VSIKerchunkKeyInfo> &GetMapKeys() const
64 : {
65 646 : return m_oMapKeys;
66 : }
67 :
68 125 : void AddInlineContent(const std::string &key, std::vector<GByte> &&abyValue)
69 : {
70 250 : VSIKerchunkKeyInfo info;
71 125 : info.abyValue = std::move(abyValue);
72 125 : m_oMapKeys[key] = std::move(info);
73 125 : }
74 :
75 128 : bool AddInlineContent(const std::string &key, const std::string_view &str)
76 : {
77 256 : std::vector<GByte> abyValue;
78 128 : if (cpl::starts_with(str, "base64:"))
79 : {
80 : abyValue.insert(
81 0 : abyValue.end(),
82 4 : reinterpret_cast<const GByte *>(str.data()) + strlen("base64:"),
83 4 : reinterpret_cast<const GByte *>(str.data()) + str.size());
84 4 : abyValue.push_back(0);
85 4 : const int nSize = CPLBase64DecodeInPlace(abyValue.data());
86 4 : if (nSize == 0)
87 : {
88 3 : CPLError(CE_Failure, CPLE_AppDefined,
89 : "VSIKerchunkJSONRefFileSystem: Base64 decoding "
90 : "failed for key '%s'",
91 : key.c_str());
92 3 : return false;
93 : }
94 1 : abyValue.resize(nSize);
95 : }
96 : else
97 : {
98 : abyValue.insert(
99 124 : abyValue.end(), reinterpret_cast<const GByte *>(str.data()),
100 248 : reinterpret_cast<const GByte *>(str.data()) + str.size());
101 : }
102 :
103 125 : AddInlineContent(key, std::move(abyValue));
104 125 : return true;
105 : }
106 :
107 20 : void AddReferencedContent(const std::string &key, const std::string &osURI,
108 : uint64_t nOffset, uint32_t nSize)
109 : {
110 20 : auto oPair = m_oSetURI.insert(osURI);
111 :
112 40 : VSIKerchunkKeyInfo info;
113 20 : info.posURI = &(*(oPair.first));
114 20 : info.nOffset = nOffset;
115 20 : info.nSize = nSize;
116 20 : m_oMapKeys[key] = std::move(info);
117 20 : }
118 :
119 : bool ConvertToParquetRef(const std::string &osCacheDir,
120 : GDALProgressFunc pfnProgress, void *pProgressData);
121 : };
122 :
123 : /************************************************************************/
124 : /* VSIKerchunkJSONRefFileSystem */
125 : /************************************************************************/
126 :
127 : class VSIKerchunkJSONRefFileSystem final : public VSIFilesystemHandler
128 : {
129 : public:
130 1776 : VSIKerchunkJSONRefFileSystem()
131 1776 : {
132 1776 : bool *pbInstantiated = &IsFileSystemInstantiated();
133 1776 : *pbInstantiated = true;
134 1776 : }
135 :
136 1126 : ~VSIKerchunkJSONRefFileSystem() override
137 1126 : {
138 1126 : bool *pbInstantiated = &IsFileSystemInstantiated();
139 1126 : *pbInstantiated = false;
140 1126 : }
141 :
142 4678 : static bool &IsFileSystemInstantiated()
143 : {
144 : static bool bIsFileSystemInstantiated = false;
145 4678 : return bIsFileSystemInstantiated;
146 : }
147 :
148 : VSIVirtualHandleUniquePtr Open(const char *pszFilename,
149 : const char *pszAccess, bool bSetError,
150 : CSLConstList papszOptions) override;
151 :
152 : int Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
153 : int nFlags) override;
154 :
155 : char **ReadDirEx(const char *pszDirname, int nMaxFiles) override;
156 :
157 : char **GetFileMetadata(const char *pszFilename, const char *pszDomain,
158 : CSLConstList papszOptions) override;
159 :
160 : private:
161 : friend bool VSIKerchunkConvertJSONToParquet(const char *pszSrcJSONFilename,
162 : const char *pszDstDirname,
163 : GDALProgressFunc pfnProgress,
164 : void *pProgressData);
165 :
166 : lru11::Cache<std::string, std::shared_ptr<VSIKerchunkRefFile>, std::mutex>
167 : m_oCache{};
168 :
169 : static std::pair<std::string, std::string>
170 : SplitFilename(const char *pszFilename);
171 :
172 : std::pair<std::shared_ptr<VSIKerchunkRefFile>, std::string>
173 : Load(const std::string &osJSONFilename, bool bUseCache);
174 : std::shared_ptr<VSIKerchunkRefFile>
175 : LoadInternal(const std::string &osJSONFilename,
176 : GDALProgressFunc pfnProgress, void *pProgressData);
177 : std::shared_ptr<VSIKerchunkRefFile>
178 : LoadStreaming(const std::string &osJSONFilename,
179 : GDALProgressFunc pfnProgress, void *pProgressData);
180 : };
181 :
182 : /************************************************************************/
183 : /* VSIKerchunkJSONRefFileSystem::SplitFilename() */
184 : /************************************************************************/
185 :
186 : /*static*/
187 : std::pair<std::string, std::string>
188 425 : VSIKerchunkJSONRefFileSystem::SplitFilename(const char *pszFilename)
189 : {
190 425 : if (STARTS_WITH(pszFilename, JSON_REF_FS_PREFIX))
191 333 : pszFilename += strlen(JSON_REF_FS_PREFIX);
192 92 : else if (STARTS_WITH(pszFilename, JSON_REF_CACHED_FS_PREFIX))
193 92 : pszFilename += strlen(JSON_REF_CACHED_FS_PREFIX);
194 : else
195 0 : return {std::string(), std::string()};
196 :
197 850 : std::string osJSONFilename;
198 :
199 425 : if (*pszFilename == '{')
200 : {
201 : // Parse /vsikerchunk_json_ref/{/path/to/some.json}[key]
202 393 : int nLevel = 1;
203 393 : ++pszFilename;
204 28165 : for (; *pszFilename; ++pszFilename)
205 : {
206 28153 : if (*pszFilename == '{')
207 : {
208 0 : ++nLevel;
209 : }
210 28153 : else if (*pszFilename == '}')
211 : {
212 381 : --nLevel;
213 381 : if (nLevel == 0)
214 : {
215 381 : ++pszFilename;
216 381 : break;
217 : }
218 : }
219 27772 : osJSONFilename += *pszFilename;
220 : }
221 393 : if (nLevel != 0)
222 : {
223 12 : CPLError(CE_Failure, CPLE_AppDefined,
224 : "Invalid %s syntax: should be "
225 : "%s{/path/to/some/file}[/optional_key]",
226 : JSON_REF_FS_PREFIX, JSON_REF_FS_PREFIX);
227 12 : return {std::string(), std::string()};
228 : }
229 :
230 : return {osJSONFilename,
231 762 : *pszFilename == '/' ? pszFilename + 1 : pszFilename};
232 : }
233 : else
234 : {
235 32 : int nCountDotJson = 0;
236 32 : const char *pszIter = pszFilename;
237 32 : const char *pszAfterJSON = nullptr;
238 54 : while ((pszIter = strstr(pszIter, ".json")) != nullptr)
239 : {
240 22 : ++nCountDotJson;
241 22 : if (nCountDotJson == 1)
242 20 : pszAfterJSON = pszIter + strlen(".json");
243 : else
244 2 : pszAfterJSON = nullptr;
245 22 : pszIter += strlen(".json");
246 : }
247 32 : if (!pszAfterJSON)
248 : {
249 14 : if (nCountDotJson >= 2)
250 : {
251 2 : CPLError(CE_Failure, CPLE_AppDefined,
252 : "Ambiguous %s syntax: should be "
253 : "%s{/path/to/some/file}[/optional_key]",
254 : JSON_REF_FS_PREFIX, JSON_REF_FS_PREFIX);
255 : }
256 : else
257 : {
258 12 : CPLError(CE_Failure, CPLE_AppDefined,
259 : "Invalid %s syntax: should be "
260 : "%s/path/to/some.json[/optional_key] or "
261 : "%s{/path/to/some/file}[/optional_key]",
262 : JSON_REF_FS_PREFIX, JSON_REF_FS_PREFIX,
263 : JSON_REF_FS_PREFIX);
264 : }
265 14 : return {std::string(), std::string()};
266 : }
267 36 : return {std::string(pszFilename, pszAfterJSON - pszFilename),
268 36 : *pszAfterJSON == '/' ? pszAfterJSON + 1 : pszAfterJSON};
269 : }
270 : }
271 :
272 : /************************************************************************/
273 : /* class VSIKerchunkJSONRefParser */
274 : /************************************************************************/
275 :
276 : namespace
277 : {
278 : class VSIKerchunkJSONRefParser final : public CPLJSonStreamingParser
279 : {
280 : public:
281 71 : explicit VSIKerchunkJSONRefParser(
282 : const std::shared_ptr<VSIKerchunkRefFile> &refFile)
283 71 : : m_refFile(refFile)
284 : {
285 71 : m_oWriter.SetPrettyFormatting(false);
286 71 : }
287 :
288 71 : ~VSIKerchunkJSONRefParser() override
289 71 : {
290 : // In case the parsing would be stopped, the writer may be in
291 : // an inconsistent state. This avoids assertion in debug mode.
292 71 : m_oWriter.clear();
293 71 : }
294 :
295 : protected:
296 78 : void String(std::string_view sValue) override
297 : {
298 78 : if (m_nLevel == m_nKeyLevel && m_nArrayLevel == 0)
299 : {
300 9 : size_t nLength = sValue.size();
301 9 : if (nLength > 0 && sValue[nLength - 1] == 0)
302 2 : --nLength;
303 :
304 9 : if (!m_refFile->AddInlineContent(
305 9 : m_osCurKey, std::string_view(sValue.data(), nLength)))
306 : {
307 2 : StopParsing();
308 : }
309 :
310 9 : m_oWriter.clear();
311 :
312 9 : m_osCurKey.clear();
313 : }
314 69 : else if (m_nLevel == m_nKeyLevel && m_nArrayLevel == 1)
315 : {
316 42 : if (m_iArrayMemberIdx == 0)
317 : {
318 36 : m_osURI = sValue;
319 : }
320 : else
321 : {
322 6 : UnexpectedContentInArray();
323 : }
324 : }
325 27 : else if (m_nLevel > m_nKeyLevel)
326 : {
327 25 : m_oWriter.Add(sValue);
328 : }
329 78 : }
330 :
331 161 : void Number(std::string_view sValue) override
332 : {
333 161 : if (m_nLevel == m_nKeyLevel)
334 : {
335 52 : if (m_nArrayLevel == 1)
336 : {
337 48 : if (m_iArrayMemberIdx == 1)
338 : {
339 27 : m_osTmpForNumber = sValue;
340 27 : errno = 0;
341 27 : m_nOffset =
342 27 : std::strtoull(m_osTmpForNumber.c_str(), nullptr, 10);
343 50 : if (errno != 0 || m_osTmpForNumber[0] == '-' ||
344 23 : m_osTmpForNumber.find('.') != std::string::npos)
345 : {
346 6 : CPLError(
347 : CE_Failure, CPLE_AppDefined,
348 : "VSIKerchunkJSONRefFileSystem: array value at "
349 : "index 1 for key '%s' is not an unsigned 64 bit "
350 : "integer",
351 : m_osCurKey.c_str());
352 6 : StopParsing();
353 : }
354 : }
355 21 : else if (m_iArrayMemberIdx == 2)
356 : {
357 19 : m_osTmpForNumber = sValue;
358 19 : errno = 0;
359 : const uint64_t nSize =
360 19 : std::strtoull(m_osTmpForNumber.c_str(), nullptr, 10);
361 19 : if (errno != 0 || m_osTmpForNumber[0] == '-' ||
362 53 : nSize > std::numeric_limits<uint32_t>::max() ||
363 15 : m_osTmpForNumber.find('.') != std::string::npos)
364 : {
365 6 : CPLError(
366 : CE_Failure, CPLE_AppDefined,
367 : "VSIKerchunkJSONRefFileSystem: array value at "
368 : "index 2 for key '%s' is not an unsigned 32 bit "
369 : "integer",
370 : m_osCurKey.c_str());
371 6 : StopParsing();
372 : }
373 : else
374 : {
375 13 : m_nSize = static_cast<uint32_t>(nSize);
376 : }
377 : }
378 : else
379 : {
380 2 : UnexpectedContentInArray();
381 : }
382 : }
383 : else
384 : {
385 4 : UnexpectedContent();
386 : }
387 : }
388 109 : else if (m_nLevel > m_nKeyLevel)
389 : {
390 108 : m_oWriter.AddSerializedValue(sValue);
391 : }
392 161 : }
393 :
394 4 : void Boolean(bool b) override
395 : {
396 4 : if (m_nLevel == m_nKeyLevel)
397 : {
398 4 : UnexpectedContent();
399 : }
400 0 : else if (m_nLevel > m_nKeyLevel)
401 : {
402 0 : m_oWriter.Add(b);
403 : }
404 4 : }
405 :
406 28 : void Null() override
407 : {
408 28 : if (m_nLevel == m_nKeyLevel)
409 : {
410 4 : UnexpectedContent();
411 : }
412 24 : else if (m_nLevel > m_nKeyLevel)
413 : {
414 24 : m_oWriter.AddNull();
415 : }
416 28 : }
417 :
418 168 : void StartObject() override
419 : {
420 168 : if (m_nLevel == m_nKeyLevel && m_nArrayLevel == 1)
421 : {
422 2 : UnexpectedContentInArray();
423 : }
424 : else
425 : {
426 166 : if (m_nLevel >= m_nKeyLevel)
427 : {
428 95 : m_oWriter.StartObj();
429 : }
430 166 : ++m_nLevel;
431 166 : m_bFirstMember = true;
432 : }
433 168 : }
434 :
435 124 : void EndObject() override
436 : {
437 124 : if (m_nLevel == m_nKeyLevel)
438 : {
439 28 : FinishObjectValueProcessing();
440 : }
441 124 : --m_nLevel;
442 124 : if (m_nLevel >= m_nKeyLevel)
443 : {
444 95 : m_oWriter.EndObj();
445 : }
446 124 : }
447 :
448 330 : void StartObjectMember(std::string_view sKey) override
449 : {
450 330 : if (m_nLevel == 1 && m_bFirstMember)
451 : {
452 81 : if (sKey == "version")
453 : {
454 3 : m_nKeyLevel = 2;
455 : }
456 : else
457 : {
458 78 : m_nKeyLevel = 1;
459 : }
460 : }
461 249 : else if (m_nLevel == 1 && m_nKeyLevel == 2 && sKey == "templates")
462 : {
463 1 : CPLError(CE_Failure, CPLE_NotSupported,
464 : "VSIKerchunkJSONRefFileSystem: 'templates' key found, but "
465 : "not supported");
466 1 : StopParsing();
467 : }
468 248 : else if (m_nLevel == 1 && m_nKeyLevel == 2 && sKey == "gen")
469 : {
470 1 : CPLError(CE_Failure, CPLE_NotSupported,
471 : "VSIKerchunkJSONRefFileSystem: 'gen' key found, but not "
472 : "supported");
473 1 : StopParsing();
474 : }
475 :
476 330 : if (m_nLevel == m_nKeyLevel)
477 : {
478 158 : FinishObjectValueProcessing();
479 158 : m_osCurKey = sKey;
480 : }
481 172 : else if (m_nLevel > m_nKeyLevel)
482 : {
483 164 : m_oWriter.AddObjKey(sKey);
484 : }
485 330 : m_bFirstMember = false;
486 330 : }
487 :
488 83 : void StartArray() override
489 : {
490 83 : if (m_nLevel == m_nKeyLevel)
491 : {
492 46 : if (m_nArrayLevel == 0)
493 : {
494 46 : m_iArrayMemberIdx = -1;
495 46 : m_osURI.clear();
496 46 : m_nOffset = 0;
497 46 : m_nSize = 0;
498 46 : m_nArrayLevel = 1;
499 : }
500 : else
501 : {
502 0 : UnexpectedContentInArray();
503 : }
504 : }
505 37 : else if (m_nLevel > m_nKeyLevel)
506 : {
507 37 : m_oWriter.StartArray();
508 37 : ++m_nArrayLevel;
509 : }
510 83 : }
511 :
512 57 : void EndArray() override
513 : {
514 57 : if (m_nLevel == m_nKeyLevel && m_nArrayLevel == 1)
515 : {
516 20 : if (m_iArrayMemberIdx == -1)
517 : {
518 2 : CPLError(CE_Failure, CPLE_AppDefined,
519 : "VSIKerchunkJSONRefFileSystem: array value for key "
520 : "'%s' is not of size 1 or 3",
521 : m_osCurKey.c_str());
522 2 : StopParsing();
523 : }
524 : else
525 : {
526 18 : m_refFile->AddReferencedContent(m_osCurKey, m_osURI, m_nOffset,
527 : m_nSize);
528 18 : --m_nArrayLevel;
529 18 : m_oWriter.clear();
530 18 : m_osCurKey.clear();
531 : }
532 : }
533 37 : else if (m_nLevel >= m_nKeyLevel)
534 : {
535 37 : --m_nArrayLevel;
536 37 : if (m_nLevel > m_nKeyLevel)
537 37 : m_oWriter.EndArray();
538 : }
539 57 : }
540 :
541 126 : void StartArrayMember() override
542 : {
543 126 : if (m_nLevel >= m_nKeyLevel)
544 126 : ++m_iArrayMemberIdx;
545 126 : }
546 :
547 2 : void Exception(const char *pszMessage) override
548 : {
549 2 : CPLError(CE_Failure, CPLE_AppDefined, "%s", pszMessage);
550 2 : }
551 :
552 : private:
553 : std::shared_ptr<VSIKerchunkRefFile> m_refFile{};
554 : int m_nLevel = 0;
555 : int m_nArrayLevel = 0;
556 : int m_iArrayMemberIdx = -1;
557 : bool m_bFirstMember = false;
558 : int m_nKeyLevel = std::numeric_limits<int>::max();
559 : std::string m_osCurKey{};
560 : std::string m_osURI{};
561 : std::string m_osTmpForNumber{};
562 : uint64_t m_nOffset = 0;
563 : uint32_t m_nSize = 0;
564 :
565 : CPLJSonStreamingWriter m_oWriter{nullptr, nullptr};
566 :
567 186 : void FinishObjectValueProcessing()
568 : {
569 186 : if (!m_osCurKey.empty())
570 : {
571 93 : const std::string &osStr = m_oWriter.GetString();
572 93 : CPL_IGNORE_RET_VAL(m_refFile->AddInlineContent(m_osCurKey, osStr));
573 :
574 93 : m_oWriter.clear();
575 :
576 93 : m_osCurKey.clear();
577 : }
578 186 : }
579 :
580 12 : void UnexpectedContent()
581 : {
582 12 : CPLError(CE_Failure, CPLE_AppDefined, "Unexpected content");
583 12 : StopParsing();
584 12 : }
585 :
586 10 : void UnexpectedContentInArray()
587 : {
588 10 : CPLError(CE_Failure, CPLE_AppDefined,
589 : "Unexpected content at position %d of array",
590 : m_iArrayMemberIdx);
591 10 : StopParsing();
592 10 : }
593 : };
594 : } // namespace
595 :
596 : /************************************************************************/
597 : /* VSIKerchunkJSONRefFileSystem::LoadStreaming() */
598 : /************************************************************************/
599 :
600 : std::shared_ptr<VSIKerchunkRefFile>
601 71 : VSIKerchunkJSONRefFileSystem::LoadStreaming(const std::string &osJSONFilename,
602 : GDALProgressFunc pfnProgress,
603 : void *pProgressData)
604 : {
605 142 : auto refFile = std::make_shared<VSIKerchunkRefFile>();
606 142 : VSIKerchunkJSONRefParser parser(refFile);
607 :
608 71 : CPLDebugOnly("VSIKerchunkJSONRefFileSystem",
609 : "Using streaming parser for %s", osJSONFilename.c_str());
610 :
611 : // For network file systems, get the streaming version of the filename,
612 : // as we don't need arbitrary seeking in the file
613 : const std::string osFilename =
614 71 : VSIFileManager::GetHandler(osJSONFilename.c_str())
615 142 : ->GetStreamingFilename(osJSONFilename);
616 :
617 142 : auto f = VSIVirtualHandleUniquePtr(VSIFOpenL(osFilename.c_str(), "rb"));
618 71 : if (!f)
619 : {
620 2 : CPLError(CE_Failure, CPLE_FileIO, "Load json file %s failed",
621 : osJSONFilename.c_str());
622 2 : return nullptr;
623 : }
624 69 : uint64_t nTotalSize = 0;
625 69 : if (!cpl::starts_with(osFilename, "/vsigzip/"))
626 : {
627 69 : f->Seek(0, SEEK_END);
628 69 : nTotalSize = f->Tell();
629 69 : f->Seek(0, SEEK_SET);
630 : }
631 138 : std::string sBuffer;
632 69 : constexpr size_t BUFFER_SIZE = 10 * 1024 * 1024; // Arbitrary
633 69 : sBuffer.resize(BUFFER_SIZE);
634 : while (true)
635 : {
636 70 : const size_t nRead = f->Read(sBuffer.data(), 1, sBuffer.size());
637 70 : const bool bFinished = nRead < sBuffer.size();
638 : try
639 : {
640 70 : if (!parser.Parse(std::string_view(sBuffer.data(), nRead),
641 : bFinished))
642 : {
643 : // The parser will have emitted an error
644 42 : return nullptr;
645 : }
646 : }
647 0 : catch (const std::exception &e)
648 : {
649 : // Out-of-memory typically
650 0 : CPLError(CE_Failure, CPLE_AppDefined,
651 : "Exception occurred while parsing %s: %s",
652 0 : osJSONFilename.c_str(), e.what());
653 0 : return nullptr;
654 : }
655 28 : if (nTotalSize)
656 : {
657 27 : const double dfProgressRatio = static_cast<double>(f->Tell()) /
658 27 : static_cast<double>(nTotalSize);
659 27 : CPLDebug("VSIKerchunkJSONRefFileSystem", "%02.1f %% of %s read",
660 : 100 * dfProgressRatio, osJSONFilename.c_str());
661 28 : if (pfnProgress &&
662 1 : !pfnProgress(dfProgressRatio, "Parsing of JSON file",
663 : pProgressData))
664 : {
665 0 : return nullptr;
666 : }
667 : }
668 : else
669 : {
670 1 : CPLDebug("VSIKerchunkJSONRefFileSystem",
671 : "%" PRIu64 " bytes read in %s",
672 1 : static_cast<uint64_t>(f->Tell()), osJSONFilename.c_str());
673 : }
674 28 : if (nRead < sBuffer.size())
675 : {
676 27 : break;
677 : }
678 1 : }
679 27 : if (f->Tell() == 0)
680 : {
681 1 : CPLError(CE_Failure, CPLE_FileIO, "Load json file %s failed",
682 : osJSONFilename.c_str());
683 1 : return nullptr;
684 : }
685 26 : if (pfnProgress)
686 1 : pfnProgress(1.0, "Parsing of JSON file", pProgressData);
687 :
688 26 : return refFile;
689 : }
690 :
691 : /************************************************************************/
692 : /* VSIKerchunkJSONRefFileSystem::LoadInternal() */
693 : /************************************************************************/
694 :
695 : std::shared_ptr<VSIKerchunkRefFile>
696 109 : VSIKerchunkJSONRefFileSystem::LoadInternal(const std::string &osJSONFilename,
697 : GDALProgressFunc pfnProgress,
698 : void *pProgressData)
699 : {
700 109 : const char *pszUseStreamingParser = VSIGetPathSpecificOption(
701 : osJSONFilename.c_str(), "VSIKERCHUNK_USE_STREAMING_PARSER", "AUTO");
702 109 : if (EQUAL(pszUseStreamingParser, "AUTO"))
703 : {
704 : auto f =
705 50 : VSIVirtualHandleUniquePtr(VSIFOpenL(osJSONFilename.c_str(), "rb"));
706 50 : if (!f)
707 : {
708 6 : CPLError(CE_Failure, CPLE_FileIO, "Load json file %s failed",
709 : osJSONFilename.c_str());
710 6 : return nullptr;
711 : }
712 44 : std::string sBuffer;
713 44 : constexpr size_t HEADER_SIZE = 1024; // Arbitrary
714 44 : sBuffer.resize(HEADER_SIZE);
715 44 : const size_t nRead = f->Read(sBuffer.data(), 1, sBuffer.size());
716 44 : sBuffer.resize(nRead);
717 44 : if (ZARRIsLikelyStreamableKerchunkJSONRefContent(sBuffer))
718 : {
719 41 : return LoadStreaming(osJSONFilename, pfnProgress, pProgressData);
720 : }
721 : }
722 59 : else if (CPLTestBool(pszUseStreamingParser))
723 : {
724 30 : return LoadStreaming(osJSONFilename, pfnProgress, pProgressData);
725 : }
726 :
727 64 : CPLJSONDocument oDoc;
728 : {
729 : #if SIZEOF_VOIDP > 4
730 32 : CPLConfigOptionSetter oSetter("CPL_JSON_MAX_SIZE", "1GB", true);
731 : #endif
732 32 : if (!oDoc.Load(osJSONFilename))
733 : {
734 5 : CPLError(CE_Failure, CPLE_AppDefined,
735 : "VSIKerchunkJSONRefFileSystem: cannot open %s",
736 : osJSONFilename.c_str());
737 5 : return nullptr;
738 : }
739 : }
740 :
741 54 : auto oRoot = oDoc.GetRoot();
742 81 : const auto oVersion = oRoot.GetObj("version");
743 54 : CPLJSONObject oRefs;
744 27 : if (!oVersion.IsValid())
745 : {
746 : // Cf https://fsspec.github.io/kerchunk/spec.html#version-0
747 :
748 23 : CPLDebugOnly("VSIKerchunkJSONRefFileSystem",
749 : "'version' key not found. Assuming version 0");
750 23 : oRefs = std::move(oRoot);
751 23 : if (!oRefs.GetObj(".zgroup").IsValid())
752 : {
753 0 : CPLError(CE_Failure, CPLE_AppDefined,
754 : "VSIKerchunkJSONRefFileSystem: '.zgroup' key not found");
755 0 : return nullptr;
756 : }
757 : }
758 4 : else if (oVersion.GetType() != CPLJSONObject::Type::Integer)
759 : {
760 4 : CPLError(CE_Failure, CPLE_AppDefined,
761 : "VSIKerchunkJSONRefFileSystem: 'version' key not integer");
762 4 : return nullptr;
763 : }
764 0 : else if (oVersion.ToInteger() != 1)
765 : {
766 0 : CPLError(CE_Failure, CPLE_NotSupported,
767 : "VSIKerchunkJSONRefFileSystem: 'version' = %d not handled",
768 : oVersion.ToInteger());
769 0 : return nullptr;
770 : }
771 : else
772 : {
773 : // Cf https://fsspec.github.io/kerchunk/spec.html#version-1
774 :
775 0 : if (oRoot.GetObj("templates").IsValid())
776 : {
777 0 : CPLError(CE_Failure, CPLE_NotSupported,
778 : "VSIKerchunkJSONRefFileSystem: 'templates' key found, but "
779 : "not supported");
780 0 : return nullptr;
781 : }
782 :
783 0 : if (oRoot.GetObj("gen").IsValid())
784 : {
785 0 : CPLError(CE_Failure, CPLE_NotSupported,
786 : "VSIKerchunkJSONRefFileSystem: 'gen' key found, but not "
787 : "supported");
788 0 : return nullptr;
789 : }
790 :
791 0 : oRefs = oRoot.GetObj("refs");
792 0 : if (!oRefs.IsValid())
793 : {
794 0 : CPLError(CE_Failure, CPLE_AppDefined,
795 : "VSIKerchunkJSONRefFileSystem: 'refs' key not found");
796 0 : return nullptr;
797 : }
798 :
799 0 : if (oRoot.GetObj("templates").IsValid())
800 : {
801 0 : CPLError(CE_Failure, CPLE_NotSupported,
802 : "VSIKerchunkJSONRefFileSystem: 'templates' key found but "
803 : "not supported");
804 0 : return nullptr;
805 : }
806 :
807 0 : if (oRoot.GetObj("templates").IsValid())
808 : {
809 0 : CPLError(CE_Failure, CPLE_NotSupported,
810 : "VSIKerchunkJSONRefFileSystem: 'templates' key found but "
811 : "not supported");
812 0 : return nullptr;
813 : }
814 : }
815 :
816 23 : if (oRefs.GetType() != CPLJSONObject::Type::Object)
817 : {
818 0 : CPLError(CE_Failure, CPLE_AppDefined,
819 : "VSIKerchunkJSONRefFileSystem: value of 'refs' is not a dict");
820 0 : return nullptr;
821 : }
822 :
823 46 : auto refFile = std::make_shared<VSIKerchunkRefFile>();
824 50 : for (const auto &oEntry : oRefs.GetChildren())
825 : {
826 45 : const std::string osKeyName = oEntry.GetName();
827 45 : if (oEntry.GetType() == CPLJSONObject::Type::String)
828 : {
829 2 : if (!refFile->AddInlineContent(osKeyName, oEntry.ToString()))
830 : {
831 1 : return nullptr;
832 : }
833 : }
834 43 : else if (oEntry.GetType() == CPLJSONObject::Type::Object)
835 : {
836 : const std::string osSerialized =
837 24 : oEntry.Format(CPLJSONObject::PrettyFormat::Plain);
838 24 : CPL_IGNORE_RET_VAL(
839 24 : refFile->AddInlineContent(osKeyName, osSerialized));
840 : }
841 19 : else if (oEntry.GetType() == CPLJSONObject::Type::Array)
842 : {
843 15 : const auto oArray = oEntry.ToArray();
844 : // Some files such as https://ncsa.osn.xsede.org/Pangeo/pangeo-forge/pangeo-forge/aws-noaa-oisst-feedstock/aws-noaa-oisst-avhrr-only.zarr/reference.json
845 : // (pointed by https://guide.cloudnativegeo.org/kerchunk/kerchunk-in-practice.html)
846 : // have array entries with just the URL, and no offset/size
847 : // This is when the whole file needs to be read
848 15 : if (oArray.Size() != 1 && oArray.Size() != 3)
849 : {
850 3 : CPLError(CE_Failure, CPLE_AppDefined,
851 : "VSIKerchunkJSONRefFileSystem: array value for key "
852 : "'%s' is not of size 1 or 3",
853 : osKeyName.c_str());
854 3 : return nullptr;
855 : }
856 12 : if (oArray[0].GetType() != CPLJSONObject::Type::String)
857 : {
858 4 : CPLError(CE_Failure, CPLE_AppDefined,
859 : "VSIKerchunkJSONRefFileSystem: array value at index 0 "
860 : "for key '%s' is not a string",
861 : osKeyName.c_str());
862 4 : return nullptr;
863 : }
864 8 : if (oArray.Size() == 3)
865 : {
866 8 : if ((oArray[1].GetType() != CPLJSONObject::Type::Integer &&
867 23 : oArray[1].GetType() != CPLJSONObject::Type::Long) ||
868 15 : !(oArray[1].ToLong() >= 0))
869 : {
870 2 : CPLError(
871 : CE_Failure, CPLE_AppDefined,
872 : "VSIKerchunkJSONRefFileSystem: array value at index 1 "
873 : "for key '%s' is not an unsigned 64 bit integer",
874 : osKeyName.c_str());
875 2 : return nullptr;
876 : }
877 6 : if ((oArray[2].GetType() != CPLJSONObject::Type::Integer &&
878 16 : oArray[2].GetType() != CPLJSONObject::Type::Long) ||
879 10 : !(oArray[2].ToLong() >= 0 &&
880 9 : static_cast<uint64_t>(oArray[2].ToLong()) <=
881 3 : std::numeric_limits<uint32_t>::max()))
882 : {
883 4 : CPLError(
884 : CE_Failure, CPLE_AppDefined,
885 : "VSIKerchunkJSONRefFileSystem: array value at index 2 "
886 : "for key '%s' is not an unsigned 32 bit integer",
887 : osKeyName.c_str());
888 4 : return nullptr;
889 : }
890 : }
891 :
892 4 : refFile->AddReferencedContent(
893 4 : osKeyName, oArray[0].ToString(),
894 4 : oArray.Size() == 3 ? oArray[1].ToLong() : 0,
895 4 : oArray.Size() == 3 ? static_cast<uint32_t>(oArray[2].ToLong())
896 : : 0);
897 : }
898 : else
899 : {
900 4 : CPLError(
901 : CE_Failure, CPLE_AppDefined,
902 : "VSIKerchunkJSONRefFileSystem: invalid value type for key '%s'",
903 : osKeyName.c_str());
904 4 : return nullptr;
905 : }
906 : }
907 :
908 5 : return refFile;
909 : }
910 :
911 : /************************************************************************/
912 : /* VSIKerchunkJSONRefFileSystem::Load() */
913 : /************************************************************************/
914 :
915 : std::pair<std::shared_ptr<VSIKerchunkRefFile>, std::string>
916 399 : VSIKerchunkJSONRefFileSystem::Load(const std::string &osJSONFilename,
917 : bool bUseCache)
918 : {
919 399 : std::shared_ptr<VSIKerchunkRefFile> refFile;
920 399 : if (m_oCache.tryGet(osJSONFilename, refFile))
921 274 : return {refFile, std::string()};
922 :
923 : // Deal with local file cache
924 125 : const char *pszUseCache = VSIGetPathSpecificOption(
925 : osJSONFilename.c_str(), "VSIKERCHUNK_USE_CACHE", "NO");
926 125 : if (bUseCache || CPLTestBool(pszUseCache))
927 : {
928 27 : if (GDALGetDriverByName("PARQUET") == nullptr)
929 : {
930 0 : CPLError(CE_Failure, CPLE_NotSupported,
931 : "VSIKERCHUNK_USE_CACHE=YES only enabled if PARQUET driver "
932 : "is available");
933 0 : return {nullptr, std::string()};
934 : }
935 :
936 : VSIStatBufL sStat;
937 54 : if (VSIStatL(osJSONFilename.c_str(), &sStat) != 0 ||
938 27 : VSI_ISDIR(sStat.st_mode))
939 : {
940 0 : CPLError(CE_Failure, CPLE_FileIO, "Load json file %s failed",
941 : osJSONFilename.c_str());
942 0 : return {nullptr, std::string()};
943 : }
944 :
945 27 : std::string osCacheSubDir = CPLGetBasenameSafe(osJSONFilename.c_str());
946 : osCacheSubDir += CPLSPrintf("_%" PRIu64 "_%" PRIu64,
947 27 : static_cast<uint64_t>(sStat.st_size),
948 27 : static_cast<uint64_t>(sStat.st_mtime));
949 :
950 27 : const std::string osRootCacheDir = GDALGetCacheDirectory();
951 27 : if (!osRootCacheDir.empty())
952 : {
953 : const std::string osKerchunkCacheDir = VSIGetPathSpecificOption(
954 : osJSONFilename.c_str(), "VSIKERCHUNK_CACHE_DIR",
955 54 : CPLFormFilenameSafe(osRootCacheDir.c_str(),
956 : "zarr_kerchunk_cache", nullptr)
957 81 : .c_str());
958 : const std::string osCacheDir = CPLFormFilenameSafe(
959 54 : osKerchunkCacheDir.c_str(), osCacheSubDir.c_str(), "zarr");
960 27 : CPLDebug("VSIKerchunkJSONRefFileSystem", "Using cache dir %s",
961 : osCacheDir.c_str());
962 :
963 54 : if (VSIStatL(CPLFormFilenameSafe(osCacheDir.c_str(), ".zmetadata",
964 : nullptr)
965 : .c_str(),
966 27 : &sStat) == 0)
967 : {
968 10 : CPLDebug("VSIKerchunkJSONRefFileSystem",
969 : "Using Kerchunk Parquet cache %s", osCacheDir.c_str());
970 10 : return {nullptr, osCacheDir};
971 : }
972 :
973 24 : if (VSIMkdirRecursive(osCacheDir.c_str(), 0755) != 0 &&
974 7 : !(VSIStatL(osCacheDir.c_str(), &sStat) == 0 &&
975 1 : VSI_ISDIR(sStat.st_mode)))
976 : {
977 6 : CPLError(CE_Failure, CPLE_AppDefined,
978 : "Cannot create directory %s", osCacheDir.c_str());
979 6 : return {nullptr, std::string()};
980 : }
981 :
982 : const std::string osLockFilename =
983 22 : CPLFormFilenameSafe(osCacheDir.c_str(), ".lock", nullptr);
984 :
985 11 : CPLLockFileHandle hLockHandle = nullptr;
986 22 : CPLStringList aosOptions;
987 11 : aosOptions.SetNameValue("VERBOSE_WAIT_MESSAGE", "YES");
988 : const char *pszKerchunkDebug =
989 11 : CPLGetConfigOption("VSIKERCHUNK_FOR_TESTS", nullptr);
990 11 : if (pszKerchunkDebug &&
991 4 : strstr(pszKerchunkDebug, "SHORT_DELAY_STALLED_LOCK"))
992 : {
993 2 : aosOptions.SetNameValue("STALLED_DELAY", "1");
994 : }
995 :
996 11 : CPLDebug("VSIKerchunkJSONRefFileSystem", "Acquiring lock");
997 11 : switch (CPLLockFileEx(osLockFilename.c_str(), &hLockHandle,
998 11 : aosOptions.List()))
999 : {
1000 10 : case CLFS_OK:
1001 10 : break;
1002 1 : case CLFS_CANNOT_CREATE_LOCK:
1003 1 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create lock %s",
1004 : osLockFilename.c_str());
1005 1 : break;
1006 0 : case CLFS_LOCK_BUSY:
1007 0 : CPLAssert(false); // cannot happen with infinite wait time
1008 : break;
1009 0 : case CLFS_API_MISUSE:
1010 0 : CPLAssert(false);
1011 : break;
1012 0 : case CLFS_THREAD_CREATION_FAILED:
1013 0 : CPLError(CE_Failure, CPLE_AppDefined,
1014 : "Thread creation failed for refresh of %s",
1015 : osLockFilename.c_str());
1016 0 : break;
1017 : }
1018 11 : if (!hLockHandle)
1019 : {
1020 1 : return {nullptr, std::string()};
1021 : }
1022 :
1023 : struct LockFileHolder
1024 : {
1025 : CPLLockFileHandle m_hLockHandle = nullptr;
1026 :
1027 10 : explicit LockFileHolder(CPLLockFileHandle hLockHandleIn)
1028 10 : : m_hLockHandle(hLockHandleIn)
1029 : {
1030 10 : }
1031 :
1032 10 : ~LockFileHolder()
1033 10 : {
1034 10 : release();
1035 10 : }
1036 :
1037 20 : void release()
1038 : {
1039 20 : if (m_hLockHandle)
1040 : {
1041 10 : CPLDebug("VSIKerchunkJSONRefFileSystem",
1042 : "Releasing lock");
1043 10 : CPLUnlockFileEx(m_hLockHandle);
1044 10 : m_hLockHandle = nullptr;
1045 : }
1046 20 : }
1047 :
1048 : CPL_DISALLOW_COPY_ASSIGN(LockFileHolder)
1049 : };
1050 :
1051 20 : LockFileHolder lockFileHolder(hLockHandle);
1052 :
1053 20 : if (VSIStatL(CPLFormFilenameSafe(osCacheDir.c_str(), ".zmetadata",
1054 : nullptr)
1055 : .c_str(),
1056 10 : &sStat) == 0)
1057 : {
1058 0 : CPLDebug("VSIKerchunkJSONRefFileSystem",
1059 : "Using Kerchunk Parquet cache %s (after lock taking)",
1060 : osCacheDir.c_str());
1061 0 : return {nullptr, osCacheDir};
1062 : }
1063 :
1064 10 : refFile = LoadInternal(osJSONFilename, nullptr, nullptr);
1065 :
1066 10 : if (refFile)
1067 : {
1068 10 : CPLDebug("VSIKerchunkJSONRefFileSystem",
1069 : "Generating Kerchunk Parquet cache %s...",
1070 : osCacheDir.c_str());
1071 :
1072 10 : if (pszKerchunkDebug &&
1073 3 : strstr(pszKerchunkDebug,
1074 : "WAIT_BEFORE_CONVERT_TO_PARQUET_REF"))
1075 : {
1076 1 : CPLSleep(0.5);
1077 : }
1078 :
1079 10 : if (refFile->ConvertToParquetRef(osCacheDir, nullptr, nullptr))
1080 : {
1081 4 : CPLDebug("VSIKerchunkJSONRefFileSystem",
1082 : "Generation Kerchunk Parquet cache %s: OK",
1083 : osCacheDir.c_str());
1084 : }
1085 : else
1086 : {
1087 6 : CPLError(CE_Failure, CPLE_AppDefined,
1088 : "Generation of Kerchunk Parquet cache %s failed",
1089 : osCacheDir.c_str());
1090 6 : refFile.reset();
1091 : }
1092 :
1093 10 : lockFileHolder.release();
1094 10 : m_oCache.insert(osJSONFilename, refFile);
1095 : }
1096 :
1097 10 : return {refFile, std::string()};
1098 : }
1099 : }
1100 :
1101 98 : refFile = LoadInternal(osJSONFilename, nullptr, nullptr);
1102 98 : if (refFile)
1103 20 : m_oCache.insert(osJSONFilename, refFile);
1104 98 : return {refFile, std::string()};
1105 : }
1106 :
1107 : /************************************************************************/
1108 : /* VSIKerchunkRefFile::ConvertToParquetRef() */
1109 : /************************************************************************/
1110 :
1111 11 : bool VSIKerchunkRefFile::ConvertToParquetRef(const std::string &osCacheDir,
1112 : GDALProgressFunc pfnProgress,
1113 : void *pProgressData)
1114 : {
1115 : struct Serializer
1116 : {
1117 : VSIVirtualHandle *m_poFile = nullptr;
1118 :
1119 11 : explicit Serializer(VSIVirtualHandle *poFile) : m_poFile(poFile)
1120 : {
1121 11 : }
1122 :
1123 289 : static void func(const char *pszTxt, void *pUserData)
1124 : {
1125 289 : Serializer *self = static_cast<Serializer *>(pUserData);
1126 289 : self->m_poFile->Write(pszTxt, 1, strlen(pszTxt));
1127 289 : }
1128 : };
1129 :
1130 : const std::string osZMetadataFilename =
1131 22 : CPLFormFilenameSafe(osCacheDir.c_str(), ".zmetadata", nullptr);
1132 22 : const std::string osZMetadataTmpFilename = osZMetadataFilename + ".tmp";
1133 : auto poFile = std::unique_ptr<VSIVirtualHandle>(
1134 22 : VSIFOpenL(osZMetadataTmpFilename.c_str(), "wb"));
1135 11 : if (!poFile)
1136 0 : return false;
1137 11 : Serializer serializer(poFile.get());
1138 :
1139 : struct ZarrArrayInfo
1140 : {
1141 : std::vector<uint64_t> anChunkCount{};
1142 : std::map<uint64_t, const VSIKerchunkKeyInfo *> chunkInfo{};
1143 : };
1144 :
1145 22 : std::map<std::string, ZarrArrayInfo> zarrArrays;
1146 :
1147 : // First pass on keys to write JSON objects in .zmetadata
1148 22 : CPLJSonStreamingWriter oWriter(Serializer::func, &serializer);
1149 11 : oWriter.StartObj();
1150 11 : oWriter.AddObjKey("metadata");
1151 11 : oWriter.StartObj();
1152 :
1153 11 : bool bOK = true;
1154 11 : size_t nCurObjectIter = 0;
1155 11 : const size_t nTotalObjects = m_oMapKeys.size();
1156 44 : for (const auto &[key, info] : m_oMapKeys)
1157 : {
1158 54 : if (cpl::ends_with(key, ".zarray") || cpl::ends_with(key, ".zgroup") ||
1159 16 : cpl::ends_with(key, ".zattrs"))
1160 : {
1161 32 : CPLJSONDocument oDoc;
1162 32 : std::string osStr;
1163 32 : osStr.append(reinterpret_cast<const char *>(info.abyValue.data()),
1164 64 : info.abyValue.size());
1165 32 : if (!oDoc.LoadMemory(osStr.c_str()))
1166 : {
1167 1 : CPLError(CE_Failure, CPLE_AppDefined,
1168 : "Cannot parse JSON content for %s", key.c_str());
1169 1 : bOK = false;
1170 1 : break;
1171 : }
1172 :
1173 31 : if (cpl::ends_with(key, ".zarray"))
1174 : {
1175 10 : const std::string osArrayName = CPLGetDirnameSafe(key.c_str());
1176 :
1177 20 : const auto oShape = oDoc.GetRoot().GetArray("shape");
1178 20 : const auto oChunks = oDoc.GetRoot().GetArray("chunks");
1179 20 : if (oShape.IsValid() && oChunks.IsValid() &&
1180 10 : oShape.Size() == oChunks.Size())
1181 : {
1182 18 : std::vector<uint64_t> anChunkCount;
1183 9 : uint64_t nTotalChunkCount = 1;
1184 17 : for (int i = 0; i < oShape.Size(); ++i)
1185 : {
1186 11 : const auto nShape = oShape[i].ToLong();
1187 11 : const auto nChunk = oChunks[i].ToLong();
1188 11 : if (nShape == 0 || nChunk == 0)
1189 : {
1190 2 : bOK = false;
1191 3 : break;
1192 : }
1193 9 : const uint64_t nChunkCount =
1194 9 : DIV_ROUND_UP(nShape, nChunk);
1195 9 : if (nChunkCount > std::numeric_limits<uint64_t>::max() /
1196 : nTotalChunkCount)
1197 : {
1198 1 : bOK = false;
1199 1 : break;
1200 : }
1201 8 : anChunkCount.push_back(nChunkCount);
1202 8 : nTotalChunkCount *= nChunkCount;
1203 : }
1204 9 : zarrArrays[osArrayName].anChunkCount =
1205 18 : std::move(anChunkCount);
1206 : }
1207 : else
1208 : {
1209 1 : bOK = false;
1210 : }
1211 10 : if (!bOK)
1212 : {
1213 4 : CPLError(CE_Failure, CPLE_AppDefined,
1214 : "Invalid Zarr array definition for %s",
1215 : osArrayName.c_str());
1216 4 : oWriter.clear();
1217 4 : break;
1218 : }
1219 : }
1220 :
1221 27 : oWriter.AddObjKey(key);
1222 27 : oWriter.AddSerializedValue(osStr);
1223 :
1224 27 : ++nCurObjectIter;
1225 31 : if (pfnProgress &&
1226 4 : !pfnProgress(static_cast<double>(nCurObjectIter) /
1227 4 : static_cast<double>(nTotalObjects),
1228 : "", pProgressData))
1229 : {
1230 0 : return false;
1231 : }
1232 : }
1233 : }
1234 :
1235 11 : constexpr int nRecordSize = 100000;
1236 :
1237 11 : if (bOK)
1238 : {
1239 6 : oWriter.EndObj();
1240 6 : oWriter.AddObjKey("record_size");
1241 6 : oWriter.Add(nRecordSize);
1242 6 : oWriter.EndObj();
1243 : }
1244 :
1245 11 : bOK = (poFile->Close() == 0) && bOK;
1246 11 : poFile.reset();
1247 :
1248 11 : if (!bOK)
1249 : {
1250 5 : oWriter.clear();
1251 5 : VSIUnlink(osZMetadataTmpFilename.c_str());
1252 5 : return false;
1253 : }
1254 :
1255 : // Second pass to retrieve chunks
1256 33 : for (const auto &[key, info] : m_oMapKeys)
1257 : {
1258 44 : if (cpl::ends_with(key, ".zarray") || cpl::ends_with(key, ".zgroup") ||
1259 16 : cpl::ends_with(key, ".zattrs"))
1260 : {
1261 : // already done
1262 : }
1263 : else
1264 : {
1265 6 : const std::string osArrayName = CPLGetDirnameSafe(key.c_str());
1266 6 : auto oIter = zarrArrays.find(osArrayName);
1267 6 : if (oIter != zarrArrays.end())
1268 : {
1269 6 : auto &oArrayInfo = oIter->second;
1270 : const CPLStringList aosIndices(
1271 6 : CSLTokenizeString2(CPLGetFilename(key.c_str()), ".", 0));
1272 6 : if ((static_cast<size_t>(aosIndices.size()) ==
1273 6 : oArrayInfo.anChunkCount.size()) ||
1274 0 : (aosIndices.size() == 1 &&
1275 0 : strcmp(aosIndices[0], "0") == 0 &&
1276 0 : oArrayInfo.anChunkCount.empty()))
1277 : {
1278 6 : std::vector<uint64_t> anIndices;
1279 11 : for (size_t i = 0; i < oArrayInfo.anChunkCount.size(); ++i)
1280 : {
1281 6 : char *endptr = nullptr;
1282 6 : anIndices.push_back(
1283 6 : std::strtoull(aosIndices[i], &endptr, 10));
1284 6 : if (aosIndices[i][0] == '-' ||
1285 12 : endptr != aosIndices[i] + strlen(aosIndices[i]) ||
1286 6 : anIndices[i] >= oArrayInfo.anChunkCount[i])
1287 : {
1288 1 : CPLError(CE_Failure, CPLE_AppDefined,
1289 : "Invalid key indices: %s", key.c_str());
1290 1 : return false;
1291 : }
1292 : }
1293 :
1294 5 : uint64_t nLinearIndex = 0;
1295 5 : uint64_t nMulFactor = 1;
1296 10 : for (size_t i = anIndices.size(); i > 0;)
1297 : {
1298 5 : --i;
1299 5 : nLinearIndex += anIndices[i] * nMulFactor;
1300 5 : nMulFactor *= oArrayInfo.anChunkCount[i];
1301 : }
1302 :
1303 : #ifdef DEBUG_VERBOSE
1304 : CPLDebugOnly("VSIKerchunkJSONRefFileSystem",
1305 : "Chunk %" PRIu64 " of array %s found",
1306 : nLinearIndex, osArrayName.c_str());
1307 : #endif
1308 5 : oArrayInfo.chunkInfo[nLinearIndex] = &info;
1309 : }
1310 : }
1311 : }
1312 : }
1313 :
1314 5 : auto poDrv = GetGDALDriverManager()->GetDriverByName("PARQUET");
1315 5 : if (!poDrv)
1316 : {
1317 : // shouldn't happen given earlier checks
1318 0 : CPLAssert(false);
1319 : return false;
1320 : }
1321 :
1322 : // Third pass to create Parquet files
1323 10 : CPLStringList aosLayerCreationOptions;
1324 : aosLayerCreationOptions.SetNameValue("ROW_GROUP_SIZE",
1325 5 : CPLSPrintf("%d", nRecordSize));
1326 :
1327 10 : for (const auto &[osArrayName, oArrayInfo] : zarrArrays)
1328 : {
1329 5 : uint64_t nChunkCount = 1;
1330 10 : for (size_t i = 0; i < oArrayInfo.anChunkCount.size(); ++i)
1331 : {
1332 5 : nChunkCount *= oArrayInfo.anChunkCount[i];
1333 : }
1334 :
1335 0 : std::unique_ptr<GDALDataset> poDS;
1336 5 : OGRLayer *poLayer = nullptr;
1337 :
1338 10 : for (uint64_t i = 0; i < nChunkCount; ++i)
1339 : {
1340 5 : bOK = poLayer != nullptr;
1341 5 : if ((i % nRecordSize) == 0)
1342 : {
1343 5 : if (poDS)
1344 : {
1345 0 : if (poDS->Close() != CE_None)
1346 : {
1347 0 : CPLError(CE_Failure, CPLE_AppDefined,
1348 : "Close() on %s failed",
1349 0 : poDS->GetDescription());
1350 0 : return false;
1351 : }
1352 0 : poDS.reset();
1353 : }
1354 5 : if (CPLHasPathTraversal(osArrayName.c_str()))
1355 : {
1356 0 : CPLError(CE_Failure, CPLE_AppDefined,
1357 : "Path traversal detected in %s",
1358 : osArrayName.c_str());
1359 0 : return false;
1360 : }
1361 : const std::string osParqFilename = CPLFormFilenameSafe(
1362 5 : CPLFormFilenameSafe(osCacheDir.c_str(), osArrayName.c_str(),
1363 : nullptr)
1364 : .c_str(),
1365 : CPLSPrintf("refs.%" PRIu64 ".parq", i / nRecordSize),
1366 10 : nullptr);
1367 5 : CPLDebugOnly("VSIKerchunkJSONRefFileSystem", "Creating %s",
1368 : osParqFilename.c_str());
1369 5 : VSIMkdirRecursive(
1370 10 : CPLGetPathSafe(osParqFilename.c_str()).c_str(), 0755);
1371 5 : poDS.reset(poDrv->Create(osParqFilename.c_str(), 0, 0, 0,
1372 : GDT_Unknown, nullptr));
1373 5 : if (!poDS)
1374 0 : return false;
1375 10 : poLayer = poDS->CreateLayer(
1376 10 : CPLGetBasenameSafe(osParqFilename.c_str()).c_str(), nullptr,
1377 5 : wkbNone, aosLayerCreationOptions.List());
1378 5 : bOK = false;
1379 5 : if (poLayer)
1380 : {
1381 : {
1382 5 : OGRFieldDefn oFieldDefn("path", OFTString);
1383 5 : bOK = poLayer->CreateField(&oFieldDefn) == OGRERR_NONE;
1384 : }
1385 : {
1386 5 : OGRFieldDefn oFieldDefn("offset", OFTInteger64);
1387 10 : bOK = bOK &&
1388 5 : poLayer->CreateField(&oFieldDefn) == OGRERR_NONE;
1389 : }
1390 : {
1391 5 : OGRFieldDefn oFieldDefn("size", OFTInteger64);
1392 10 : bOK = bOK &&
1393 5 : poLayer->CreateField(&oFieldDefn) == OGRERR_NONE;
1394 : }
1395 : {
1396 5 : OGRFieldDefn oFieldDefn("raw", OFTBinary);
1397 10 : bOK = bOK &&
1398 5 : poLayer->CreateField(&oFieldDefn) == OGRERR_NONE;
1399 : }
1400 : }
1401 : }
1402 5 : if (!bOK)
1403 0 : return false;
1404 :
1405 : auto poFeature =
1406 5 : std::make_unique<OGRFeature>(poLayer->GetLayerDefn());
1407 5 : auto oIter = oArrayInfo.chunkInfo.find(i);
1408 5 : if (oIter != oArrayInfo.chunkInfo.end())
1409 : {
1410 5 : const auto *chunkInfo = oIter->second;
1411 5 : if (chunkInfo->posURI)
1412 5 : poFeature->SetField(0, chunkInfo->posURI->c_str());
1413 5 : poFeature->SetField(1,
1414 5 : static_cast<GIntBig>(chunkInfo->nOffset));
1415 5 : poFeature->SetField(2, static_cast<GIntBig>(chunkInfo->nSize));
1416 5 : if (!chunkInfo->abyValue.empty())
1417 : {
1418 0 : if (chunkInfo->abyValue.size() >
1419 : static_cast<size_t>(INT_MAX))
1420 : {
1421 0 : CPLError(CE_Failure, CPLE_NotSupported,
1422 : "Too big blob for chunk %" PRIu64
1423 : " of array %s",
1424 : i, osArrayName.c_str());
1425 0 : return false;
1426 : }
1427 0 : poFeature->SetField(
1428 0 : 3, static_cast<int>(chunkInfo->abyValue.size()),
1429 0 : static_cast<const void *>(chunkInfo->abyValue.data()));
1430 : }
1431 : }
1432 :
1433 5 : if (poLayer->CreateFeature(poFeature.get()) != OGRERR_NONE)
1434 : {
1435 0 : CPLError(CE_Failure, CPLE_AppDefined,
1436 : "CreateFeature() on %s failed",
1437 0 : poDS->GetDescription());
1438 0 : return false;
1439 : }
1440 :
1441 5 : ++nCurObjectIter;
1442 5 : if (pfnProgress && (nCurObjectIter % 1000) == 0 &&
1443 0 : !pfnProgress(static_cast<double>(nCurObjectIter) /
1444 0 : static_cast<double>(nTotalObjects),
1445 : "", pProgressData))
1446 : {
1447 0 : return false;
1448 : }
1449 : }
1450 :
1451 5 : if (poDS)
1452 : {
1453 5 : if (poDS->Close() != CE_None)
1454 : {
1455 0 : CPLError(CE_Failure, CPLE_AppDefined, "Close() on %s failed",
1456 0 : poDS->GetDescription());
1457 0 : return false;
1458 : }
1459 5 : poDS.reset();
1460 : }
1461 : }
1462 :
1463 5 : if (VSIRename(osZMetadataTmpFilename.c_str(),
1464 5 : osZMetadataFilename.c_str()) != 0)
1465 : {
1466 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot rename %s to %s",
1467 : osZMetadataTmpFilename.c_str(), osZMetadataFilename.c_str());
1468 0 : return false;
1469 : }
1470 :
1471 5 : if (pfnProgress)
1472 1 : pfnProgress(1.0, "", pProgressData);
1473 :
1474 5 : return true;
1475 : }
1476 :
1477 : /************************************************************************/
1478 : /* VSIKerchunkConvertJSONToParquet() */
1479 : /************************************************************************/
1480 :
1481 1 : bool VSIKerchunkConvertJSONToParquet(const char *pszSrcJSONFilename,
1482 : const char *pszDstDirname,
1483 : GDALProgressFunc pfnProgress,
1484 : void *pProgressData)
1485 : {
1486 1 : if (GDALGetDriverByName("PARQUET") == nullptr)
1487 : {
1488 0 : CPLError(CE_Failure, CPLE_NotSupported,
1489 : "Conversion to a Parquet reference store is not possible "
1490 : "because the PARQUET driver is not available.");
1491 0 : return false;
1492 : }
1493 :
1494 1 : auto poFS = cpl::down_cast<VSIKerchunkJSONRefFileSystem *>(
1495 : VSIFileManager::GetHandler(JSON_REF_FS_PREFIX));
1496 1 : std::shared_ptr<VSIKerchunkRefFile> refFile;
1497 1 : if (!poFS->m_oCache.tryGet(pszSrcJSONFilename, refFile))
1498 : {
1499 : void *pScaledProgressData =
1500 1 : GDALCreateScaledProgress(0.0, 0.5, pfnProgress, pProgressData);
1501 : try
1502 : {
1503 2 : refFile = poFS->LoadInternal(
1504 : pszSrcJSONFilename,
1505 : pScaledProgressData ? GDALScaledProgress : nullptr,
1506 1 : pScaledProgressData);
1507 : }
1508 0 : catch (const std::exception &e)
1509 : {
1510 0 : CPLError(CE_Failure, CPLE_AppDefined,
1511 : "VSIKerchunkJSONRefFileSystem::Load() failed: %s",
1512 0 : e.what());
1513 0 : GDALDestroyScaledProgress(pScaledProgressData);
1514 0 : return false;
1515 : }
1516 1 : GDALDestroyScaledProgress(pScaledProgressData);
1517 : }
1518 1 : if (!refFile)
1519 : {
1520 0 : CPLError(CE_Failure, CPLE_AppDefined,
1521 : "%s is not a Kerchunk JSON reference store",
1522 : pszSrcJSONFilename);
1523 0 : return false;
1524 : }
1525 :
1526 1 : VSIMkdir(pszDstDirname, 0755);
1527 :
1528 : void *pScaledProgressData =
1529 1 : GDALCreateScaledProgress(0.5, 1.0, pfnProgress, pProgressData);
1530 1 : const bool bRet = refFile->ConvertToParquetRef(
1531 : pszDstDirname, pScaledProgressData ? GDALScaledProgress : nullptr,
1532 : pScaledProgressData);
1533 1 : GDALDestroyScaledProgress(pScaledProgressData);
1534 1 : return bRet;
1535 : }
1536 :
1537 : /************************************************************************/
1538 : /* VSIKerchunkJSONRefFileSystem::Open() */
1539 : /************************************************************************/
1540 :
1541 : VSIVirtualHandleUniquePtr
1542 91 : VSIKerchunkJSONRefFileSystem::Open(const char *pszFilename,
1543 : const char *pszAccess, bool /* bSetError */,
1544 : CSLConstList /* papszOptions */)
1545 : {
1546 91 : CPLDebugOnly("VSIKerchunkJSONRefFileSystem", "Open(%s)", pszFilename);
1547 91 : if (strcmp(pszAccess, "r") != 0 && strcmp(pszAccess, "rb") != 0)
1548 0 : return nullptr;
1549 :
1550 182 : const auto [osJSONFilename, osKey] = SplitFilename(pszFilename);
1551 91 : if (osJSONFilename.empty())
1552 4 : return nullptr;
1553 :
1554 87 : const auto [refFile, osParqFilename] = Load(
1555 174 : osJSONFilename, STARTS_WITH(pszFilename, JSON_REF_CACHED_FS_PREFIX));
1556 87 : if (!refFile)
1557 : {
1558 6 : if (osParqFilename.empty())
1559 2 : return nullptr;
1560 :
1561 : return VSIFilesystemHandler::OpenStatic(
1562 8 : CPLFormFilenameSafe(CPLSPrintf("%s{%s}", PARQUET_REF_FS_PREFIX,
1563 : osParqFilename.c_str()),
1564 : osKey.c_str(), nullptr)
1565 : .c_str(),
1566 4 : pszAccess);
1567 : }
1568 :
1569 81 : const auto oIter = refFile->GetMapKeys().find(osKey);
1570 81 : if (oIter == refFile->GetMapKeys().end())
1571 6 : return nullptr;
1572 :
1573 75 : const auto &keyInfo = oIter->second;
1574 75 : if (!keyInfo.posURI)
1575 : {
1576 : return VSIVirtualHandleUniquePtr(VSIFileFromMemBuffer(
1577 55 : nullptr, const_cast<GByte *>(keyInfo.abyValue.data()),
1578 110 : keyInfo.abyValue.size(), /* bTakeOwnership = */ false));
1579 : }
1580 : else
1581 : {
1582 : std::string osVSIPath = VSIKerchunkMorphURIToVSIPath(
1583 40 : *(keyInfo.posURI), CPLGetPathSafe(osJSONFilename.c_str()));
1584 20 : if (osVSIPath.empty())
1585 2 : return nullptr;
1586 : const std::string osPath =
1587 18 : keyInfo.nSize
1588 7 : ? CPLSPrintf("/vsisubfile/%" PRIu64 "_%u,%s", keyInfo.nOffset,
1589 7 : keyInfo.nSize, osVSIPath.c_str())
1590 36 : : std::move(osVSIPath);
1591 18 : CPLDebugOnly("VSIKerchunkJSONRefFileSystem", "Opening %s",
1592 : osPath.c_str());
1593 : CPLConfigOptionSetter oSetter("GDAL_DISABLE_READDIR_ON_OPEN",
1594 36 : "EMPTY_DIR", false);
1595 36 : auto fp = VSIFilesystemHandler::OpenStatic(osPath.c_str(), "rb", true);
1596 18 : if (!fp)
1597 : {
1598 1 : if (!VSIToCPLError(CE_Failure, CPLE_FileIO))
1599 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s",
1600 : osPath.c_str());
1601 : }
1602 18 : return fp;
1603 : }
1604 : }
1605 :
1606 : /************************************************************************/
1607 : /* VSIKerchunkJSONRefFileSystem::Stat() */
1608 : /************************************************************************/
1609 :
1610 302 : int VSIKerchunkJSONRefFileSystem::Stat(const char *pszFilename,
1611 : VSIStatBufL *pStatBuf, int nFlags)
1612 : {
1613 302 : CPLDebugOnly("VSIKerchunkJSONRefFileSystem", "Stat(%s)", pszFilename);
1614 302 : memset(pStatBuf, 0, sizeof(VSIStatBufL));
1615 :
1616 604 : const auto [osJSONFilename, osKey] = SplitFilename(pszFilename);
1617 302 : if (osJSONFilename.empty())
1618 14 : return -1;
1619 :
1620 288 : const auto [refFile, osParqFilename] = Load(
1621 576 : osJSONFilename, STARTS_WITH(pszFilename, JSON_REF_CACHED_FS_PREFIX));
1622 288 : if (!refFile)
1623 : {
1624 116 : if (osParqFilename.empty())
1625 110 : return -1;
1626 :
1627 6 : return VSIStatExL(
1628 12 : CPLFormFilenameSafe(CPLSPrintf("%s{%s}", PARQUET_REF_FS_PREFIX,
1629 : osParqFilename.c_str()),
1630 : osKey.c_str(), nullptr)
1631 : .c_str(),
1632 6 : pStatBuf, nFlags);
1633 : }
1634 :
1635 172 : if (osKey.empty())
1636 : {
1637 27 : pStatBuf->st_mode = S_IFDIR;
1638 27 : return 0;
1639 : }
1640 :
1641 145 : const auto oIter = refFile->GetMapKeys().find(osKey);
1642 145 : if (oIter == refFile->GetMapKeys().end())
1643 : {
1644 261 : if (cpl::contains(refFile->GetMapKeys(), osKey + "/.zgroup") ||
1645 174 : cpl::contains(refFile->GetMapKeys(), osKey + "/.zarray"))
1646 : {
1647 8 : pStatBuf->st_mode = S_IFDIR;
1648 8 : return 0;
1649 : }
1650 :
1651 79 : return -1;
1652 : }
1653 :
1654 58 : const auto &keyInfo = oIter->second;
1655 58 : if (!(keyInfo.posURI))
1656 : {
1657 50 : pStatBuf->st_size = keyInfo.abyValue.size();
1658 : }
1659 : else
1660 : {
1661 8 : if (keyInfo.nSize)
1662 : {
1663 4 : pStatBuf->st_size = keyInfo.nSize;
1664 : }
1665 : else
1666 : {
1667 : const std::string osVSIPath = VSIKerchunkMorphURIToVSIPath(
1668 8 : *(keyInfo.posURI), CPLGetPathSafe(osJSONFilename.c_str()));
1669 4 : if (osVSIPath.empty())
1670 0 : return -1;
1671 4 : return VSIStatExL(osVSIPath.c_str(), pStatBuf, nFlags);
1672 : }
1673 : }
1674 54 : pStatBuf->st_mode = S_IFREG;
1675 :
1676 54 : return 0;
1677 : }
1678 :
1679 : /************************************************************************/
1680 : /* VSIKerchunkJSONRefFileSystem::GetFileMetadata() */
1681 : /************************************************************************/
1682 :
1683 : char **
1684 11 : VSIKerchunkJSONRefFileSystem::GetFileMetadata(const char *pszFilename,
1685 : const char *pszDomain,
1686 : CSLConstList /* papszOptions */)
1687 : {
1688 11 : if (!pszDomain || !EQUAL(pszDomain, "CHUNK_INFO"))
1689 2 : return nullptr;
1690 :
1691 18 : const auto [osJSONFilename, osKey] = SplitFilename(pszFilename);
1692 9 : if (osJSONFilename.empty() || osKey.empty())
1693 4 : return nullptr;
1694 :
1695 5 : const auto [refFile, osParqFilename] = Load(
1696 10 : osJSONFilename, STARTS_WITH(pszFilename, JSON_REF_CACHED_FS_PREFIX));
1697 5 : if (!refFile)
1698 0 : return nullptr;
1699 :
1700 5 : const auto oIter = refFile->GetMapKeys().find(osKey);
1701 5 : if (oIter == refFile->GetMapKeys().end())
1702 2 : return nullptr;
1703 :
1704 3 : const auto &keyInfo = oIter->second;
1705 6 : CPLStringList aosMetadata;
1706 3 : if (!(keyInfo.posURI))
1707 : {
1708 : aosMetadata.SetNameValue(
1709 : "SIZE", CPLSPrintf(CPL_FRMT_GUIB,
1710 0 : static_cast<GUIntBig>(keyInfo.abyValue.size())));
1711 0 : if (keyInfo.abyValue.size() <
1712 0 : static_cast<size_t>(std::numeric_limits<int>::max() - 1))
1713 : {
1714 : char *pszBase64 =
1715 0 : CPLBase64Encode(static_cast<int>(keyInfo.abyValue.size()),
1716 : keyInfo.abyValue.data());
1717 0 : aosMetadata.SetNameValue("BASE64", pszBase64);
1718 0 : CPLFree(pszBase64);
1719 : }
1720 : }
1721 : else
1722 : {
1723 : const std::string osVSIPath = VSIKerchunkMorphURIToVSIPath(
1724 3 : *(keyInfo.posURI), CPLGetPathSafe(osJSONFilename.c_str()));
1725 3 : if (osVSIPath.empty())
1726 0 : return nullptr;
1727 3 : if (keyInfo.nSize)
1728 : {
1729 1 : aosMetadata.SetNameValue("SIZE", CPLSPrintf("%u", keyInfo.nSize));
1730 : }
1731 : else
1732 : {
1733 : VSIStatBufL sStatBuf;
1734 2 : if (VSIStatL(osVSIPath.c_str(), &sStatBuf) != 0)
1735 0 : return nullptr;
1736 : aosMetadata.SetNameValue(
1737 : "SIZE", CPLSPrintf(CPL_FRMT_GUIB,
1738 2 : static_cast<GUIntBig>(sStatBuf.st_size)));
1739 : }
1740 : aosMetadata.SetNameValue(
1741 : "OFFSET",
1742 3 : CPLSPrintf(CPL_FRMT_GUIB, static_cast<GUIntBig>(keyInfo.nOffset)));
1743 3 : aosMetadata.SetNameValue("FILENAME", osVSIPath.c_str());
1744 : }
1745 :
1746 3 : return aosMetadata.StealList();
1747 : }
1748 :
1749 : /************************************************************************/
1750 : /* VSIKerchunkJSONRefFileSystem::ReadDirEx() */
1751 : /************************************************************************/
1752 :
1753 23 : char **VSIKerchunkJSONRefFileSystem::ReadDirEx(const char *pszDirname,
1754 : int nMaxFiles)
1755 : {
1756 23 : CPLDebugOnly("VSIKerchunkJSONRefFileSystem", "ReadDir(%s)", pszDirname);
1757 :
1758 46 : const auto [osJSONFilename, osAskedKey] = SplitFilename(pszDirname);
1759 23 : if (osJSONFilename.empty())
1760 4 : return nullptr;
1761 :
1762 19 : const auto [refFile, osParqFilename] = Load(
1763 38 : osJSONFilename, STARTS_WITH(pszDirname, JSON_REF_CACHED_FS_PREFIX));
1764 19 : if (!refFile)
1765 : {
1766 9 : if (osParqFilename.empty())
1767 9 : return nullptr;
1768 :
1769 0 : return VSIReadDirEx(
1770 0 : CPLFormFilenameSafe(CPLSPrintf("%s{%s}", PARQUET_REF_FS_PREFIX,
1771 : osParqFilename.c_str()),
1772 : osAskedKey.c_str(), nullptr)
1773 : .c_str(),
1774 0 : nMaxFiles);
1775 : }
1776 :
1777 20 : std::set<std::string> set;
1778 60 : for (const auto &[key, value] : refFile->GetMapKeys())
1779 : {
1780 50 : if (osAskedKey.empty())
1781 : {
1782 20 : const auto nPos = key.find('/');
1783 20 : if (nPos == std::string::npos)
1784 8 : set.insert(key);
1785 : else
1786 12 : set.insert(key.substr(0, nPos));
1787 : }
1788 30 : else if (key.size() > osAskedKey.size() &&
1789 42 : cpl::starts_with(key, osAskedKey) &&
1790 12 : key[osAskedKey.size()] == '/')
1791 : {
1792 24 : std::string subKey = key.substr(osAskedKey.size() + 1);
1793 12 : const auto nPos = subKey.find('/');
1794 12 : if (nPos == std::string::npos)
1795 12 : set.insert(std::move(subKey));
1796 : else
1797 0 : set.insert(subKey.substr(0, nPos));
1798 : }
1799 : }
1800 :
1801 20 : CPLStringList aosRet;
1802 34 : for (const std::string &v : set)
1803 : {
1804 : // CPLDebugOnly("VSIKerchunkJSONRefFileSystem", ".. %s", v.c_str());
1805 24 : aosRet.AddString(v.c_str());
1806 : }
1807 10 : return aosRet.StealList();
1808 : }
1809 :
1810 : /************************************************************************/
1811 : /* VSIInstallKerchunkJSONRefFileSystem() */
1812 : /************************************************************************/
1813 :
1814 1776 : void VSIInstallKerchunkJSONRefFileSystem()
1815 : {
1816 : static std::mutex oMutex;
1817 3552 : std::lock_guard<std::mutex> oLock(oMutex);
1818 : // cppcheck-suppress knownConditionTrueFalse
1819 1776 : if (!VSIKerchunkJSONRefFileSystem::IsFileSystemInstantiated())
1820 : {
1821 1776 : auto fs = std::make_shared<VSIKerchunkJSONRefFileSystem>();
1822 1776 : VSIFileManager::InstallHandler(JSON_REF_FS_PREFIX, fs);
1823 1776 : VSIFileManager::InstallHandler(JSON_REF_CACHED_FS_PREFIX, fs);
1824 : }
1825 1776 : }
|