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