Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: VSI Virtual File System
4 : * Purpose: Implementation of caching IO layer.
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2011, Frank Warmerdam <warmerdam@pobox.com>
9 : * Copyright (c) 2011-2014, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #include "cpl_port.h"
15 : #include "cpl_vsi_virtual.h"
16 :
17 : #include <cstddef>
18 : #include <cstdlib>
19 : #include <cstring>
20 :
21 : #include <algorithm>
22 : #include <limits>
23 : #include <map>
24 : #include <memory>
25 : #include <utility>
26 :
27 : #include "cpl_conv.h"
28 : #include "cpl_error.h"
29 : #include "cpl_vsi.h"
30 : #include "cpl_vsi_virtual.h"
31 : #include "cpl_mem_cache.h"
32 : #include "cpl_noncopyablevector.h"
33 :
34 : //! @cond Doxygen_Suppress
35 :
36 : /************************************************************************/
37 : /* ==================================================================== */
38 : /* VSICachedFile */
39 : /* ==================================================================== */
40 : /************************************************************************/
41 :
42 : class VSICachedFile final : public VSIVirtualHandle
43 : {
44 : CPL_DISALLOW_COPY_ASSIGN(VSICachedFile)
45 :
46 : public:
47 : VSICachedFile(VSIVirtualHandle *poBaseHandle, size_t nChunkSize,
48 : size_t nCacheSize);
49 :
50 64 : ~VSICachedFile() override
51 32 : {
52 32 : VSICachedFile::Close();
53 64 : }
54 :
55 : bool LoadBlocks(vsi_l_offset nStartBlock, size_t nBlockCount, void *pBuffer,
56 : size_t nBufferSize);
57 :
58 : VSIVirtualHandleUniquePtr m_poBase{};
59 :
60 : vsi_l_offset m_nOffset = 0;
61 : vsi_l_offset m_nFileSize = 0;
62 :
63 : size_t m_nChunkSize = 0;
64 : lru11::Cache<vsi_l_offset, cpl::NonCopyableVector<GByte>>
65 : m_oCache; // can only been initialized in constructor
66 :
67 : bool m_bEOF = false;
68 : bool m_bError = false;
69 :
70 : int Seek(vsi_l_offset nOffset, int nWhence) override;
71 : vsi_l_offset Tell() override;
72 : size_t Read(void *pBuffer, size_t nBytes) override;
73 : int ReadMultiRange(int nRanges, void **ppData,
74 : const vsi_l_offset *panOffsets,
75 : const size_t *panSizes) override;
76 :
77 0 : void AdviseRead(int nRanges, const vsi_l_offset *panOffsets,
78 : const size_t *panSizes) override
79 : {
80 0 : m_poBase->AdviseRead(nRanges, panOffsets, panSizes);
81 0 : }
82 :
83 : size_t Write(const void *pBuffer, size_t nBytes) override;
84 : void ClearErr() override;
85 : int Eof() override;
86 : int Error() override;
87 : int Flush() override;
88 : int Close() override;
89 :
90 0 : void *GetNativeFileDescriptor() override
91 : {
92 0 : return m_poBase->GetNativeFileDescriptor();
93 : }
94 :
95 0 : bool HasPRead() const override
96 : {
97 0 : return m_poBase->HasPRead();
98 : }
99 :
100 0 : size_t PRead(void *pBuffer, size_t nSize,
101 : vsi_l_offset nOffset) const override
102 : {
103 0 : return m_poBase->PRead(pBuffer, nSize, nOffset);
104 : }
105 : };
106 :
107 : /************************************************************************/
108 : /* GetCacheMax() */
109 : /************************************************************************/
110 :
111 32 : static size_t GetCacheMax(size_t nCacheSize)
112 : {
113 32 : if (nCacheSize)
114 : {
115 0 : return nCacheSize;
116 : }
117 :
118 32 : const char *pszCacheSize = CPLGetConfigOption("VSI_CACHE_SIZE", "25000000");
119 : GIntBig nMemorySize;
120 : bool bUnitSpecified;
121 32 : if (CPLParseMemorySize(pszCacheSize, &nMemorySize, &bUnitSpecified) !=
122 : CE_None)
123 : {
124 0 : CPLError(
125 : CE_Failure, CPLE_IllegalArg,
126 : "Failed to parse value of VSI_CACHE_SIZE. Using default of 25MB");
127 0 : nMemorySize = 25000000;
128 : }
129 32 : else if (static_cast<size_t>(nMemorySize) >
130 32 : std::numeric_limits<size_t>::max() / 2)
131 : {
132 0 : nMemorySize =
133 0 : static_cast<GIntBig>(std::numeric_limits<size_t>::max() / 2);
134 : }
135 :
136 32 : return static_cast<size_t>(nMemorySize);
137 : }
138 :
139 : /************************************************************************/
140 : /* VSICachedFile() */
141 : /************************************************************************/
142 :
143 32 : VSICachedFile::VSICachedFile(VSIVirtualHandle *poBaseHandle, size_t nChunkSize,
144 32 : size_t nCacheSize)
145 : : m_poBase(poBaseHandle),
146 32 : m_nChunkSize(nChunkSize ? nChunkSize : VSI_CACHED_DEFAULT_CHUNK_SIZE),
147 64 : m_oCache{cpl::div_round_up(GetCacheMax(nCacheSize), m_nChunkSize), 0}
148 : {
149 32 : m_poBase->Seek(0, SEEK_END);
150 32 : m_nFileSize = m_poBase->Tell();
151 32 : }
152 :
153 : /************************************************************************/
154 : /* Close() */
155 : /************************************************************************/
156 :
157 65 : int VSICachedFile::Close()
158 :
159 : {
160 65 : m_oCache.clear();
161 65 : m_poBase.reset();
162 :
163 65 : return 0;
164 : }
165 :
166 : /************************************************************************/
167 : /* Seek() */
168 : /************************************************************************/
169 :
170 10747 : int VSICachedFile::Seek(vsi_l_offset nReqOffset, int nWhence)
171 :
172 : {
173 10747 : m_bEOF = false;
174 :
175 10747 : if (nWhence == SEEK_SET)
176 : {
177 : // Use offset directly.
178 : }
179 7977 : else if (nWhence == SEEK_CUR)
180 : {
181 7937 : nReqOffset += m_nOffset;
182 : }
183 40 : else if (nWhence == SEEK_END)
184 : {
185 40 : nReqOffset += m_nFileSize;
186 : }
187 :
188 10747 : m_nOffset = nReqOffset;
189 :
190 10747 : return 0;
191 : }
192 :
193 : /************************************************************************/
194 : /* Tell() */
195 : /************************************************************************/
196 :
197 8012 : vsi_l_offset VSICachedFile::Tell()
198 :
199 : {
200 8012 : return m_nOffset;
201 : }
202 :
203 : /************************************************************************/
204 : /* LoadBlocks() */
205 : /* */
206 : /* Load the desired set of blocks. Use pBuffer as a temporary */
207 : /* buffer if it would be helpful. */
208 : /************************************************************************/
209 :
210 482 : bool VSICachedFile::LoadBlocks(vsi_l_offset nStartBlock, size_t nBlockCount,
211 : void *pBuffer, size_t nBufferSize)
212 :
213 : {
214 482 : if (nBlockCount == 0)
215 0 : return true;
216 :
217 : /* -------------------------------------------------------------------- */
218 : /* When we want to load only one block, we can directly load it */
219 : /* into the target buffer with no concern about intermediaries. */
220 : /* -------------------------------------------------------------------- */
221 482 : if (nBlockCount == 1)
222 : {
223 455 : if (m_poBase->Seek(static_cast<vsi_l_offset>(nStartBlock) *
224 455 : m_nChunkSize,
225 455 : SEEK_SET) != 0)
226 : {
227 0 : return false;
228 : }
229 :
230 : try
231 : {
232 455 : cpl::NonCopyableVector<GByte> oData(m_nChunkSize);
233 : const auto nDataRead =
234 455 : m_poBase->Read(oData.data(), 1, m_nChunkSize);
235 455 : if (nDataRead == 0)
236 6 : return false;
237 449 : if (nDataRead < m_nChunkSize && m_poBase->Error())
238 0 : m_bError = true;
239 449 : oData.resize(nDataRead);
240 :
241 449 : m_oCache.insert(nStartBlock, std::move(oData));
242 : }
243 0 : catch (const std::exception &)
244 : {
245 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
246 : "Out of memory situation in VSICachedFile::LoadBlocks()");
247 0 : return false;
248 : }
249 :
250 449 : return true;
251 : }
252 :
253 : /* -------------------------------------------------------------------- */
254 : /* If the buffer is quite large but not quite large enough to */
255 : /* hold all the blocks we will take the pain of splitting the */
256 : /* io request in two in order to avoid allocating a large */
257 : /* temporary buffer. */
258 : /* -------------------------------------------------------------------- */
259 27 : if (nBufferSize > m_nChunkSize * 20 &&
260 15 : nBufferSize < nBlockCount * m_nChunkSize)
261 : {
262 0 : if (!LoadBlocks(nStartBlock, 2, pBuffer, nBufferSize))
263 0 : return false;
264 :
265 0 : return LoadBlocks(nStartBlock + 2, nBlockCount - 2, pBuffer,
266 0 : nBufferSize);
267 : }
268 :
269 27 : if (m_poBase->Seek(static_cast<vsi_l_offset>(nStartBlock) * m_nChunkSize,
270 27 : SEEK_SET) != 0)
271 0 : return false;
272 :
273 : /* -------------------------------------------------------------------- */
274 : /* Do we need to allocate our own buffer? */
275 : /* -------------------------------------------------------------------- */
276 27 : GByte *pabyWorkBuffer = static_cast<GByte *>(pBuffer);
277 :
278 27 : if (nBufferSize < m_nChunkSize * nBlockCount)
279 : {
280 : pabyWorkBuffer = static_cast<GByte *>(
281 4 : VSI_MALLOC_VERBOSE(m_nChunkSize * nBlockCount));
282 4 : if (pabyWorkBuffer == nullptr)
283 0 : return false;
284 : }
285 :
286 : /* -------------------------------------------------------------------- */
287 : /* Read the whole request into the working buffer. */
288 : /* -------------------------------------------------------------------- */
289 :
290 27 : const size_t nToRead = nBlockCount * m_nChunkSize;
291 27 : const size_t nDataRead = m_poBase->Read(pabyWorkBuffer, 1, nToRead);
292 27 : if (nDataRead < nToRead && m_poBase->Error())
293 0 : m_bError = true;
294 :
295 27 : bool ret = true;
296 27 : if (nToRead > nDataRead + m_nChunkSize - 1)
297 : {
298 5 : size_t nNewBlockCount = cpl::div_round_up(nDataRead, m_nChunkSize);
299 5 : if (nNewBlockCount < nBlockCount)
300 : {
301 5 : nBlockCount = nNewBlockCount;
302 5 : ret = false;
303 : }
304 : }
305 :
306 552 : for (size_t i = 0; i < nBlockCount; i++)
307 : {
308 525 : const vsi_l_offset iBlock = nStartBlock + i;
309 :
310 1050 : const auto nDataFilled = (nDataRead >= (i + 1) * m_nChunkSize)
311 525 : ? m_nChunkSize
312 4 : : nDataRead - i * m_nChunkSize;
313 : try
314 : {
315 1050 : cpl::NonCopyableVector<GByte> oData(nDataFilled);
316 :
317 525 : memcpy(oData.data(), pabyWorkBuffer + i * m_nChunkSize,
318 : nDataFilled);
319 :
320 525 : m_oCache.insert(iBlock, std::move(oData));
321 : }
322 0 : catch (const std::exception &)
323 : {
324 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
325 : "Out of memory situation in VSICachedFile::LoadBlocks()");
326 0 : ret = false;
327 0 : break;
328 : }
329 : }
330 :
331 27 : if (pabyWorkBuffer != pBuffer)
332 4 : CPLFree(pabyWorkBuffer);
333 :
334 27 : return ret;
335 : }
336 :
337 : /************************************************************************/
338 : /* Read() */
339 : /************************************************************************/
340 :
341 11571 : size_t VSICachedFile::Read(void *pBuffer, size_t nBytes)
342 :
343 : {
344 11571 : if (nBytes == 0)
345 0 : return 0;
346 11571 : const size_t nRequestedBytes = nBytes;
347 :
348 : // nFileSize might be set wrongly to 0 by underlying layers, such as
349 : // /vsicurl_streaming/https://query.data.world/s/jgsghstpphjhicstradhy5kpjwrnfy
350 11571 : if (m_nFileSize > 0 && m_nOffset >= m_nFileSize)
351 : {
352 7 : m_bEOF = true;
353 7 : return 0;
354 : }
355 :
356 : /* ==================================================================== */
357 : /* Make sure the cache is loaded for the whole request region. */
358 : /* ==================================================================== */
359 11564 : const vsi_l_offset nStartBlock = m_nOffset / m_nChunkSize;
360 : // Calculate last block
361 11564 : const vsi_l_offset nLastBlock = m_nFileSize / m_nChunkSize;
362 11564 : vsi_l_offset nEndBlock = (m_nOffset + nRequestedBytes - 1) / m_nChunkSize;
363 :
364 : // if nLastBlock is not 0 consider the min value to avoid out-of-range reads
365 11564 : if (nLastBlock != 0 && nEndBlock > nLastBlock)
366 : {
367 7 : nEndBlock = nLastBlock;
368 : }
369 :
370 23701 : for (vsi_l_offset iBlock = nStartBlock; iBlock <= nEndBlock; iBlock++)
371 : {
372 12143 : if (!m_oCache.contains(iBlock))
373 : {
374 433 : size_t nBlocksToLoad = 1;
375 1477 : while (iBlock + nBlocksToLoad <= nEndBlock &&
376 1477 : !m_oCache.contains(iBlock + nBlocksToLoad))
377 : {
378 518 : nBlocksToLoad++;
379 : }
380 :
381 433 : if (!LoadBlocks(iBlock, nBlocksToLoad, pBuffer, nRequestedBytes))
382 6 : break;
383 : }
384 : }
385 :
386 : /* ==================================================================== */
387 : /* Copy data into the target buffer to the extent possible. */
388 : /* ==================================================================== */
389 11564 : size_t nAmountCopied = 0;
390 :
391 23803 : while (nAmountCopied < nRequestedBytes)
392 : {
393 12255 : const vsi_l_offset iBlock = (m_nOffset + nAmountCopied) / m_nChunkSize;
394 12255 : const cpl::NonCopyableVector<GByte> *poData = m_oCache.getPtr(iBlock);
395 12255 : if (poData == nullptr)
396 : {
397 : // We can reach that point when the amount to read exceeds
398 : // the cache size.
399 49 : LoadBlocks(iBlock, 1, static_cast<GByte *>(pBuffer) + nAmountCopied,
400 49 : std::min(nRequestedBytes - nAmountCopied, m_nChunkSize));
401 49 : poData = m_oCache.getPtr(iBlock);
402 49 : if (poData == nullptr)
403 : {
404 16 : break;
405 : }
406 : }
407 :
408 12250 : const vsi_l_offset nStartOffset =
409 12250 : static_cast<vsi_l_offset>(iBlock) * m_nChunkSize;
410 12250 : if (nStartOffset + poData->size() < nAmountCopied + m_nOffset)
411 1 : break;
412 : const size_t nThisCopy =
413 24498 : std::min(nRequestedBytes - nAmountCopied,
414 12249 : static_cast<size_t>(((nStartOffset + poData->size()) -
415 12249 : nAmountCopied - m_nOffset)));
416 12249 : if (nThisCopy == 0)
417 10 : break;
418 :
419 12239 : memcpy(static_cast<GByte *>(pBuffer) + nAmountCopied,
420 12239 : poData->data() + (m_nOffset + nAmountCopied) - nStartOffset,
421 : nThisCopy);
422 :
423 12239 : nAmountCopied += nThisCopy;
424 : }
425 :
426 11564 : m_nOffset += nAmountCopied;
427 :
428 11564 : const size_t nRet = nAmountCopied;
429 11564 : if (nRet != nBytes && !m_bError)
430 16 : m_bEOF = true;
431 11564 : return nRet;
432 : }
433 :
434 : /************************************************************************/
435 : /* ReadMultiRange() */
436 : /************************************************************************/
437 :
438 0 : int VSICachedFile::ReadMultiRange(int const nRanges, void **const ppData,
439 : const vsi_l_offset *const panOffsets,
440 : const size_t *const panSizes)
441 : {
442 : // If the base is /vsicurl/
443 0 : return m_poBase->ReadMultiRange(nRanges, ppData, panOffsets, panSizes);
444 : }
445 :
446 : /************************************************************************/
447 : /* Write() */
448 : /************************************************************************/
449 :
450 0 : size_t VSICachedFile::Write(const void * /* pBuffer */, size_t /*nBytes */)
451 : {
452 0 : return 0;
453 : }
454 :
455 : /************************************************************************/
456 : /* ClearErr() */
457 : /************************************************************************/
458 :
459 1 : void VSICachedFile::ClearErr()
460 :
461 : {
462 1 : m_poBase->ClearErr();
463 1 : m_bEOF = false;
464 1 : m_bError = false;
465 1 : }
466 :
467 : /************************************************************************/
468 : /* Error() */
469 : /************************************************************************/
470 :
471 2 : int VSICachedFile::Error()
472 :
473 : {
474 2 : return m_bError;
475 : }
476 :
477 : /************************************************************************/
478 : /* Eof() */
479 : /************************************************************************/
480 :
481 2 : int VSICachedFile::Eof()
482 :
483 : {
484 2 : return m_bEOF;
485 : }
486 :
487 : /************************************************************************/
488 : /* Flush() */
489 : /************************************************************************/
490 :
491 2 : int VSICachedFile::Flush()
492 :
493 : {
494 2 : return 0;
495 : }
496 :
497 : /************************************************************************/
498 : /* VSICachedFilesystemHandler */
499 : /************************************************************************/
500 :
501 : class VSICachedFilesystemHandler final : public VSIFilesystemHandler
502 : {
503 : static bool AnalyzeFilename(const char *pszFilename,
504 : std::string &osUnderlyingFilename,
505 : size_t &nChunkSize, size_t &nCacheSize);
506 :
507 : public:
508 : VSIVirtualHandleUniquePtr Open(const char *pszFilename,
509 : const char *pszAccess, bool bSetError,
510 : CSLConstList papszOptions) override;
511 : int Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
512 : int nFlags) override;
513 : char **ReadDirEx(const char *pszDirname, int nMaxFiles) override;
514 : };
515 :
516 : /************************************************************************/
517 : /* ParseSize() */
518 : /************************************************************************/
519 :
520 17 : static bool ParseSize(const char *pszKey, const char *pszValue, size_t nMaxVal,
521 : size_t &nOutVal)
522 : {
523 17 : char *end = nullptr;
524 17 : auto nVal = std::strtoull(pszValue, &end, 10);
525 17 : if (!end || end == pszValue || nVal >= nMaxVal)
526 : {
527 6 : CPLError(
528 : CE_Failure, CPLE_IllegalArg,
529 : "Invalid value for %s: %s. Max supported value = " CPL_FRMT_GUIB,
530 : pszKey, pszValue, static_cast<GUIntBig>(nMaxVal));
531 6 : return false;
532 : }
533 11 : if (*end != '\0')
534 : {
535 10 : if (strcmp(end, "KB") == 0)
536 : {
537 4 : if (nVal > nMaxVal / 1024)
538 : {
539 2 : CPLError(CE_Failure, CPLE_IllegalArg,
540 : "Invalid value for %s: %s. Max supported value "
541 : "= " CPL_FRMT_GUIB,
542 : pszKey, pszValue, static_cast<GUIntBig>(nMaxVal));
543 2 : return false;
544 : }
545 2 : nVal *= 1024;
546 : }
547 6 : else if (strcmp(end, "MB") == 0)
548 : {
549 4 : if (nVal > nMaxVal / (1024 * 1024))
550 : {
551 2 : CPLError(CE_Failure, CPLE_IllegalArg,
552 : "Invalid value for %s: %s. Max supported value "
553 : "= " CPL_FRMT_GUIB,
554 : pszKey, pszValue, static_cast<GUIntBig>(nMaxVal));
555 2 : return false;
556 : }
557 2 : nVal *= (1024 * 1024);
558 : }
559 : else
560 : {
561 2 : CPLError(CE_Failure, CPLE_IllegalArg, "Invalid value for %s: %s",
562 : pszKey, pszValue);
563 2 : return false;
564 : }
565 : }
566 5 : nOutVal = static_cast<size_t>(nVal);
567 5 : return true;
568 : }
569 :
570 : /************************************************************************/
571 : /* AnalyzeFilename() */
572 : /************************************************************************/
573 :
574 26 : bool VSICachedFilesystemHandler::AnalyzeFilename(
575 : const char *pszFilename, std::string &osUnderlyingFilename,
576 : size_t &nChunkSize, size_t &nCacheSize)
577 : {
578 :
579 26 : if (!STARTS_WITH(pszFilename, "/vsicached?"))
580 0 : return false;
581 :
582 : const CPLStringList aosTokens(
583 52 : CSLTokenizeString2(pszFilename + strlen("/vsicached?"), "&", 0));
584 :
585 26 : osUnderlyingFilename.clear();
586 26 : nChunkSize = 0;
587 26 : nCacheSize = 0;
588 :
589 42 : for (int i = 0; i < aosTokens.size(); ++i)
590 : {
591 : char *pszUnescaped =
592 28 : CPLUnescapeString(aosTokens[i], nullptr, CPLES_URL);
593 28 : std::string osUnescaped(pszUnescaped);
594 28 : CPLFree(pszUnescaped);
595 28 : char *pszKey = nullptr;
596 28 : const char *pszValue = CPLParseNameValue(osUnescaped.c_str(), &pszKey);
597 28 : if (pszKey && pszValue)
598 : {
599 28 : if (strcmp(pszKey, "file") == 0)
600 : {
601 10 : osUnderlyingFilename = pszValue;
602 : }
603 18 : else if (strcmp(pszKey, "chunk_size") == 0)
604 : {
605 11 : if (!ParseSize(pszKey, pszValue, 1024 * 1024 * 1024,
606 : nChunkSize))
607 : {
608 6 : CPLFree(pszKey);
609 6 : return false;
610 : }
611 : }
612 7 : else if (strcmp(pszKey, "cache_size") == 0)
613 : {
614 6 : if (!ParseSize(pszKey, pszValue,
615 : std::numeric_limits<size_t>::max(), nCacheSize))
616 : {
617 6 : CPLFree(pszKey);
618 6 : return false;
619 : }
620 : }
621 : else
622 : {
623 1 : CPLError(CE_Warning, CPLE_NotSupported,
624 : "Unsupported option: %s", pszKey);
625 : }
626 : }
627 16 : CPLFree(pszKey);
628 : }
629 :
630 14 : if (osUnderlyingFilename.empty())
631 : {
632 4 : CPLError(CE_Warning, CPLE_NotSupported, "Missing 'file' option");
633 : }
634 :
635 14 : return !osUnderlyingFilename.empty();
636 : }
637 :
638 : /************************************************************************/
639 : /* Open() */
640 : /************************************************************************/
641 :
642 : VSIVirtualHandleUniquePtr
643 22 : VSICachedFilesystemHandler::Open(const char *pszFilename, const char *pszAccess,
644 : bool bSetError, CSLConstList papszOptions)
645 : {
646 44 : std::string osUnderlyingFilename;
647 22 : size_t nChunkSize = 0;
648 22 : size_t nCacheSize = 0;
649 22 : if (!AnalyzeFilename(pszFilename, osUnderlyingFilename, nChunkSize,
650 : nCacheSize))
651 14 : return nullptr;
652 8 : if (strcmp(pszAccess, "r") != 0 && strcmp(pszAccess, "rb") != 0)
653 : {
654 1 : if (bSetError)
655 : {
656 0 : VSIError(VSIE_FileError,
657 : "/vsicached? supports only 'r' and 'rb' access modes");
658 : }
659 1 : return nullptr;
660 : }
661 :
662 : auto fp = VSIFilesystemHandler::OpenStatic(
663 14 : osUnderlyingFilename.c_str(), pszAccess, bSetError, papszOptions);
664 7 : if (!fp)
665 1 : return nullptr;
666 : return VSIVirtualHandleUniquePtr(
667 6 : VSICreateCachedFile(fp.release(), nChunkSize, nCacheSize));
668 : }
669 :
670 : /************************************************************************/
671 : /* Stat() */
672 : /************************************************************************/
673 :
674 2 : int VSICachedFilesystemHandler::Stat(const char *pszFilename,
675 : VSIStatBufL *pStatBuf, int nFlags)
676 : {
677 4 : std::string osUnderlyingFilename;
678 2 : size_t nChunkSize = 0;
679 2 : size_t nCacheSize = 0;
680 2 : if (!AnalyzeFilename(pszFilename, osUnderlyingFilename, nChunkSize,
681 : nCacheSize))
682 1 : return -1;
683 1 : return VSIStatExL(osUnderlyingFilename.c_str(), pStatBuf, nFlags);
684 : }
685 :
686 : /************************************************************************/
687 : /* ReadDirEx() */
688 : /************************************************************************/
689 :
690 2 : char **VSICachedFilesystemHandler::ReadDirEx(const char *pszDirname,
691 : int nMaxFiles)
692 : {
693 4 : std::string osUnderlyingFilename;
694 2 : size_t nChunkSize = 0;
695 2 : size_t nCacheSize = 0;
696 2 : if (!AnalyzeFilename(pszDirname, osUnderlyingFilename, nChunkSize,
697 : nCacheSize))
698 1 : return nullptr;
699 1 : return VSIReadDirEx(osUnderlyingFilename.c_str(), nMaxFiles);
700 : }
701 :
702 : //! @endcond
703 :
704 : /************************************************************************/
705 : /* VSICreateCachedFile() */
706 : /************************************************************************/
707 :
708 : /** Wraps a file handle in another one, which has caching for read-operations.
709 : *
710 : * This takes a virtual file handle and returns a new handle that caches
711 : * read-operations on the input file handle. The cache is RAM based and
712 : * the content of the cache is discarded when the file handle is closed.
713 : * The cache is a least-recently used lists of blocks of 32KB each.
714 : *
715 : * @param poBaseHandle base handle
716 : * @param nChunkSize chunk size, in bytes. If 0, defaults to 32 KB
717 : * @param nCacheSize total size of the cache for the file, in bytes.
718 : * If 0, defaults to the value of the VSI_CACHE_SIZE
719 : * configuration option, which defaults to 25 MB.
720 : * @return a new handle
721 : */
722 32 : VSIVirtualHandle *VSICreateCachedFile(VSIVirtualHandle *poBaseHandle,
723 : size_t nChunkSize, size_t nCacheSize)
724 :
725 : {
726 32 : return new VSICachedFile(poBaseHandle, nChunkSize, nCacheSize);
727 : }
728 :
729 : /************************************************************************/
730 : /* VSIInstallCachedFileHandler() */
731 : /************************************************************************/
732 :
733 : /*!
734 : \brief Install /vsicached? file system handler
735 :
736 : \verbatim embed:rst
737 : See :ref:`/vsicached? documentation <vsicached>`
738 : \endverbatim
739 :
740 : @since GDAL 3.8.0
741 : */
742 2025 : void VSIInstallCachedFileHandler(void)
743 : {
744 2025 : VSIFileManager::InstallHandler(
745 4050 : "/vsicached?", std::make_shared<VSICachedFilesystemHandler>());
746 2025 : }
|