LCOV - code coverage report
Current view: top level - frmts/wms - gdalwmscache.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 145 158 91.8 %
Date: 2025-01-18 12:42:00 Functions: 17 17 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  WMS Client Driver
       4             :  * Purpose:  Implementation of Dataset and RasterBand classes for WMS
       5             :  *           and other similar services.
       6             :  * Author:   Adam Nowacki, nowak@xpam.de
       7             :  *
       8             :  ******************************************************************************
       9             :  * Copyright (c) 2007, Adam Nowacki
      10             :  * Copyright (c) 2017, Dmitry Baryshnikov, <polimax@mail.ru>
      11             :  * Copyright (c) 2017, NextGIS, <info@nextgis.com>
      12             :  *
      13             :  * SPDX-License-Identifier: MIT
      14             :  ****************************************************************************/
      15             : 
      16             : #include "cpl_md5.h"
      17             : #include "wmsdriver.h"
      18             : 
      19          12 : static void CleanCacheThread(void *pData)
      20             : {
      21          12 :     GDALWMSCache *pCache = static_cast<GDALWMSCache *>(pData);
      22          12 :     pCache->Clean();
      23          12 : }
      24             : 
      25             : //------------------------------------------------------------------------------
      26             : // GDALWMSFileCache
      27             : //------------------------------------------------------------------------------
      28             : class GDALWMSFileCache : public GDALWMSCacheImpl
      29             : {
      30             :   public:
      31         213 :     GDALWMSFileCache(const CPLString &soPath, CPLXMLNode *pConfig)
      32         213 :         : GDALWMSCacheImpl(soPath, pConfig), m_osPostfix(""), m_nDepth(2),
      33             :           m_nExpires(604800),            // 7 days
      34             :           m_nMaxSize(67108864),          // 64 Mb
      35         213 :           m_nCleanThreadRunTimeout(120)  // 3 min
      36             :     {
      37         213 :         const char *pszCacheDepth = CPLGetXMLValue(pConfig, "Depth", "2");
      38         213 :         if (pszCacheDepth != nullptr)
      39         213 :             m_nDepth = atoi(pszCacheDepth);
      40             : 
      41             :         const char *pszCacheExtension =
      42         213 :             CPLGetXMLValue(pConfig, "Extension", nullptr);
      43         213 :         if (pszCacheExtension != nullptr)
      44           0 :             m_osPostfix = pszCacheExtension;
      45             : 
      46             :         const char *pszCacheExpires =
      47         213 :             CPLGetXMLValue(pConfig, "Expires", nullptr);
      48         213 :         if (pszCacheExpires != nullptr)
      49             :         {
      50           1 :             m_nExpires = atoi(pszCacheExpires);
      51           1 :             CPLDebug("WMS", "Cache expires in %d sec", m_nExpires);
      52             :         }
      53             : 
      54             :         const char *pszCacheMaxSize =
      55         213 :             CPLGetXMLValue(pConfig, "MaxSize", nullptr);
      56         213 :         if (pszCacheMaxSize != nullptr)
      57           0 :             m_nMaxSize = atol(pszCacheMaxSize);
      58             : 
      59             :         const char *pszCleanThreadRunTimeout =
      60         213 :             CPLGetXMLValue(pConfig, "CleanTimeout", nullptr);
      61         213 :         if (pszCleanThreadRunTimeout != nullptr)
      62             :         {
      63           0 :             m_nCleanThreadRunTimeout = atoi(pszCleanThreadRunTimeout);
      64           0 :             CPLDebug("WMS", "Clean Thread Run Timeout is %d sec",
      65             :                      m_nCleanThreadRunTimeout);
      66             :         }
      67         213 :     }
      68             : 
      69          18 :     virtual int GetCleanThreadRunTimeout() override
      70             :     {
      71          18 :         return m_nCleanThreadRunTimeout;
      72             :     }
      73             : 
      74          18 :     virtual CPLErr Insert(const char *pszKey,
      75             :                           const CPLString &osFileName) override
      76             :     {
      77             :         // Warns if it fails to write, but returns success
      78          36 :         CPLString soFilePath = GetFilePath(pszKey);
      79          18 :         MakeDirs(CPLGetDirnameSafe(soFilePath).c_str());
      80          18 :         if (CPLCopyFile(soFilePath, osFileName) == CE_None)
      81          17 :             return CE_None;
      82             :         // Warn if it fails after folder creation
      83           1 :         CPLError(CE_Warning, CPLE_FileIO, "Error writing to WMS cache %s",
      84             :                  m_soPath.c_str());
      85           1 :         return CE_None;
      86             :     }
      87             : 
      88             :     virtual enum GDALWMSCacheItemStatus
      89          32 :     GetItemStatus(const char *pszKey) const override
      90             :     {
      91             :         VSIStatBufL sStatBuf;
      92          32 :         if (VSIStatL(GetFilePath(pszKey), &sStatBuf) == 0)
      93             :         {
      94          16 :             long seconds = static_cast<long>(time(nullptr) - sStatBuf.st_mtime);
      95          16 :             return seconds < m_nExpires ? CACHE_ITEM_OK : CACHE_ITEM_EXPIRED;
      96             :         }
      97          16 :         return CACHE_ITEM_NOT_FOUND;
      98             :     }
      99             : 
     100          14 :     virtual GDALDataset *GetDataset(const char *pszKey,
     101             :                                     char **papszOpenOptions) const override
     102             :     {
     103          14 :         return GDALDataset::FromHandle(GDALOpenEx(
     104          28 :             GetFilePath(pszKey),
     105             :             GDAL_OF_RASTER | GDAL_OF_READONLY | GDAL_OF_VERBOSE_ERROR, nullptr,
     106          28 :             papszOpenOptions, nullptr));
     107             :     }
     108             : 
     109          12 :     virtual void Clean() override
     110             :     {
     111          12 :         char **papszList = VSIReadDirRecursive(m_soPath);
     112          12 :         if (papszList == nullptr)
     113             :         {
     114           1 :             return;
     115             :         }
     116             : 
     117          11 :         int counter = 0;
     118          22 :         std::vector<int> toDelete;
     119          11 :         long nSize = 0;
     120          11 :         time_t nTime = time(nullptr);
     121          56 :         while (papszList[counter] != nullptr)
     122             :         {
     123             :             const std::string osPath =
     124          45 :                 CPLFormFilenameSafe(m_soPath, papszList[counter], nullptr);
     125             :             VSIStatBufL sStatBuf;
     126          45 :             if (VSIStatL(osPath.c_str(), &sStatBuf) == 0)
     127             :             {
     128          45 :                 if (!VSI_ISDIR(sStatBuf.st_mode))
     129             :                 {
     130          15 :                     long seconds = static_cast<long>(nTime - sStatBuf.st_mtime);
     131          15 :                     if (seconds > m_nExpires)
     132             :                     {
     133           3 :                         toDelete.push_back(counter);
     134             :                     }
     135             : 
     136          15 :                     nSize += static_cast<long>(sStatBuf.st_size);
     137             :                 }
     138             :             }
     139          45 :             counter++;
     140             :         }
     141             : 
     142          11 :         if (nSize > m_nMaxSize)
     143             :         {
     144           0 :             CPLDebug("WMS", "Delete %u items from cache",
     145           0 :                      static_cast<unsigned int>(toDelete.size()));
     146           0 :             for (size_t i = 0; i < toDelete.size(); ++i)
     147             :             {
     148             :                 const std::string osPath = CPLFormFilenameSafe(
     149           0 :                     m_soPath, papszList[toDelete[i]], nullptr);
     150           0 :                 VSIUnlink(osPath.c_str());
     151             :             }
     152             :         }
     153             : 
     154          11 :         CSLDestroy(papszList);
     155             :     }
     156             : 
     157             :   private:
     158          64 :     CPLString GetFilePath(const char *pszKey) const
     159             :     {
     160         128 :         CPLString soHash(CPLMD5String(pszKey));
     161          64 :         CPLString soCacheFile(m_soPath);
     162             : 
     163          64 :         if (!soCacheFile.empty() && soCacheFile.back() != '/')
     164             :         {
     165          64 :             soCacheFile.append(1, '/');
     166             :         }
     167             : 
     168         192 :         for (int i = 0; i < m_nDepth; ++i)
     169             :         {
     170         128 :             soCacheFile.append(1, soHash[i]);
     171         128 :             soCacheFile.append(1, '/');
     172             :         }
     173          64 :         soCacheFile.append(soHash);
     174          64 :         soCacheFile.append(m_osPostfix);
     175         128 :         return soCacheFile;
     176             :     }
     177             : 
     178          60 :     static void MakeDirs(const char *pszPath)
     179             :     {
     180          60 :         if (IsPathExists(pszPath))
     181             :         {
     182          18 :             return;
     183             :         }
     184             :         // Recursive makedirs, ignoring errors
     185          42 :         MakeDirs(CPLGetDirnameSafe(pszPath).c_str());
     186             : 
     187          42 :         VSIMkdir(pszPath, 0744);
     188             :     }
     189             : 
     190          60 :     static bool IsPathExists(const char *pszPath)
     191             :     {
     192             :         VSIStatBufL sbuf;
     193          60 :         return VSIStatL(pszPath, &sbuf) == 0;
     194             :     }
     195             : 
     196             :   private:
     197             :     CPLString m_osPostfix;
     198             :     int m_nDepth;
     199             :     int m_nExpires;
     200             :     long m_nMaxSize;
     201             :     int m_nCleanThreadRunTimeout;
     202             : };
     203             : 
     204             : //------------------------------------------------------------------------------
     205             : // GDALWMSCache
     206             : //------------------------------------------------------------------------------
     207             : 
     208             : GDALWMSCache::GDALWMSCache() = default;
     209             : 
     210         213 : GDALWMSCache::~GDALWMSCache()
     211             : {
     212         213 :     if (m_hThread)
     213          12 :         CPLJoinThread(m_hThread);
     214         213 :     delete m_poCache;
     215         213 : }
     216             : 
     217         213 : CPLErr GDALWMSCache::Initialize(const char *pszUrl, CPLXMLNode *pConfig)
     218             : {
     219         354 :     const auto NullifyIfEmpty = [](const char *pszStr)
     220         354 :     { return pszStr && pszStr[0] != 0 ? pszStr : nullptr; };
     221             : 
     222         213 :     if (const char *pszXmlCachePath =
     223         213 :             NullifyIfEmpty(CPLGetXMLValue(pConfig, "Path", nullptr)))
     224             :     {
     225         112 :         m_osCachePath = pszXmlCachePath;
     226             :     }
     227         101 :     else if (const char *pszUserCachePath = NullifyIfEmpty(
     228             :                  CPLGetConfigOption("GDAL_DEFAULT_WMS_CACHE_PATH", nullptr)))
     229             :     {
     230          90 :         m_osCachePath = pszUserCachePath;
     231             :     }
     232          11 :     else if (const char *pszXDG_CACHE_HOME =
     233          11 :                  NullifyIfEmpty(CPLGetConfigOption("XDG_CACHE_HOME", nullptr)))
     234             :     {
     235             :         m_osCachePath =
     236           1 :             CPLFormFilenameSafe(pszXDG_CACHE_HOME, "gdalwmscache", nullptr);
     237             :     }
     238             :     else
     239             :     {
     240             : #ifdef _WIN32
     241             :         const char *pszHome =
     242             :             NullifyIfEmpty(CPLGetConfigOption("USERPROFILE", nullptr));
     243             : #else
     244             :         const char *pszHome =
     245          10 :             NullifyIfEmpty(CPLGetConfigOption("HOME", nullptr));
     246             : #endif
     247          10 :         if (pszHome)
     248             :         {
     249           5 :             m_osCachePath = CPLFormFilenameSafe(
     250          10 :                 CPLFormFilenameSafe(pszHome, ".cache", nullptr).c_str(),
     251           5 :                 "gdalwmscache", nullptr);
     252             :         }
     253             :         else
     254             :         {
     255             :             const char *pszDir =
     256           5 :                 NullifyIfEmpty(CPLGetConfigOption("CPL_TMPDIR", nullptr));
     257             : 
     258           5 :             if (!pszDir)
     259           3 :                 pszDir = NullifyIfEmpty(CPLGetConfigOption("TMPDIR", nullptr));
     260             : 
     261           5 :             if (!pszDir)
     262           2 :                 pszDir = NullifyIfEmpty(CPLGetConfigOption("TEMP", nullptr));
     263             : 
     264           5 :             if (!pszDir)
     265           1 :                 pszDir = ".";
     266             : 
     267             :             const char *pszUsername =
     268           5 :                 NullifyIfEmpty(CPLGetConfigOption("USERNAME", nullptr));
     269           5 :             if (!pszUsername)
     270             :                 pszUsername =
     271           4 :                     NullifyIfEmpty(CPLGetConfigOption("USER", nullptr));
     272             : 
     273           5 :             if (pszUsername)
     274             :             {
     275           8 :                 m_osCachePath = CPLFormFilenameSafe(
     276             :                     pszDir, CPLSPrintf("gdalwmscache_%s", pszUsername),
     277           4 :                     nullptr);
     278             :             }
     279             :             else
     280             :             {
     281           2 :                 m_osCachePath = CPLFormFilenameSafe(
     282             :                     pszDir,
     283             :                     CPLSPrintf("gdalwmscache_%s",
     284             :                                CPLMD5String(pszUrl ? pszUrl : "")),
     285           1 :                     nullptr);
     286             :             }
     287             :         }
     288             :     }
     289             : 
     290             :     // Separate folder for each unique dataset url
     291         213 :     if (CPLTestBool(CPLGetXMLValue(pConfig, "Unique", "True")))
     292             :     {
     293         210 :         m_osCachePath = CPLFormFilenameSafe(
     294         105 :             m_osCachePath, CPLMD5String(pszUrl ? pszUrl : ""), nullptr);
     295             :     }
     296         213 :     CPLDebug("WMS", "Using %s for cache", m_osCachePath.c_str());
     297             : 
     298             :     // TODO: Add sqlite db cache type
     299         213 :     const char *pszType = CPLGetXMLValue(pConfig, "Type", "file");
     300         213 :     if (EQUAL(pszType, "file"))
     301             :     {
     302         213 :         m_poCache = new GDALWMSFileCache(m_osCachePath, pConfig);
     303             :     }
     304             : 
     305         213 :     return CE_None;
     306             : }
     307             : 
     308          18 : CPLErr GDALWMSCache::Insert(const char *pszKey, const CPLString &soFileName)
     309             : {
     310          18 :     if (m_poCache != nullptr && pszKey != nullptr)
     311             :     {
     312             :         // Add file to cache
     313          18 :         CPLErr result = m_poCache->Insert(pszKey, soFileName);
     314          18 :         if (result == CE_None)
     315             :         {
     316             :             // Start clean thread
     317          18 :             int cleanThreadRunTimeout = m_poCache->GetCleanThreadRunTimeout();
     318          36 :             if (cleanThreadRunTimeout > 0 && !m_bIsCleanThreadRunning &&
     319          18 :                 time(nullptr) - m_nCleanThreadLastRunTime >
     320          18 :                     cleanThreadRunTimeout)
     321             :             {
     322          12 :                 if (m_hThread)
     323           0 :                     CPLJoinThread(m_hThread);
     324          12 :                 m_bIsCleanThreadRunning = true;
     325          12 :                 m_hThread = CPLCreateJoinableThread(CleanCacheThread, this);
     326             :             }
     327             :         }
     328          18 :         return result;
     329             :     }
     330             : 
     331           0 :     return CE_Failure;
     332             : }
     333             : 
     334             : enum GDALWMSCacheItemStatus
     335          32 : GDALWMSCache::GetItemStatus(const char *pszKey) const
     336             : {
     337          32 :     if (m_poCache != nullptr)
     338             :     {
     339          32 :         return m_poCache->GetItemStatus(pszKey);
     340             :     }
     341           0 :     return CACHE_ITEM_NOT_FOUND;
     342             : }
     343             : 
     344          14 : GDALDataset *GDALWMSCache::GetDataset(const char *pszKey,
     345             :                                       char **papszOpenOptions) const
     346             : {
     347          14 :     if (m_poCache != nullptr)
     348             :     {
     349          14 :         return m_poCache->GetDataset(pszKey, papszOpenOptions);
     350             :     }
     351           0 :     return nullptr;
     352             : }
     353             : 
     354          12 : void GDALWMSCache::Clean()
     355             : {
     356          12 :     if (m_poCache != nullptr)
     357             :     {
     358          12 :         CPLDebug("WMS", "Clean cache");
     359          12 :         m_poCache->Clean();
     360             :     }
     361             : 
     362          12 :     m_nCleanThreadLastRunTime = time(nullptr);
     363          12 :     m_bIsCleanThreadRunning = false;
     364          12 : }

Generated by: LCOV version 1.14