LCOV - code coverage report
Current view: top level - frmts/wms - gdalwmscache.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 140 154 90.9 %
Date: 2024-11-21 22:18:42 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(CPLGetDirname(soFilePath));
      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 char *pszPath =
     124          45 :                 CPLFormFilename(m_soPath, papszList[counter], nullptr);
     125             :             VSIStatBufL sStatBuf;
     126          45 :             if (VSIStatL(pszPath, &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           0 :                         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 char *pszPath =
     149           0 :                     CPLFormFilename(m_soPath, papszList[toDelete[i]], nullptr);
     150           0 :                 VSIUnlink(pszPath);
     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 :         const char *pszDirPath = CPLGetDirname(pszPath);
     186          42 :         MakeDirs(pszDirPath);
     187             : 
     188          42 :         VSIMkdir(pszPath, 0744);
     189             :     }
     190             : 
     191          60 :     static bool IsPathExists(const char *pszPath)
     192             :     {
     193             :         VSIStatBufL sbuf;
     194          60 :         return VSIStatL(pszPath, &sbuf) == 0;
     195             :     }
     196             : 
     197             :   private:
     198             :     CPLString m_osPostfix;
     199             :     int m_nDepth;
     200             :     int m_nExpires;
     201             :     long m_nMaxSize;
     202             :     int m_nCleanThreadRunTimeout;
     203             : };
     204             : 
     205             : //------------------------------------------------------------------------------
     206             : // GDALWMSCache
     207             : //------------------------------------------------------------------------------
     208             : 
     209             : GDALWMSCache::GDALWMSCache() = default;
     210             : 
     211         213 : GDALWMSCache::~GDALWMSCache()
     212             : {
     213         213 :     if (m_hThread)
     214          12 :         CPLJoinThread(m_hThread);
     215         213 :     delete m_poCache;
     216         213 : }
     217             : 
     218         213 : CPLErr GDALWMSCache::Initialize(const char *pszUrl, CPLXMLNode *pConfig)
     219             : {
     220         354 :     const auto NullifyIfEmpty = [](const char *pszStr)
     221         354 :     { return pszStr && pszStr[0] != 0 ? pszStr : nullptr; };
     222             : 
     223         213 :     if (const char *pszXmlCachePath =
     224         213 :             NullifyIfEmpty(CPLGetXMLValue(pConfig, "Path", nullptr)))
     225             :     {
     226         112 :         m_osCachePath = pszXmlCachePath;
     227             :     }
     228         101 :     else if (const char *pszUserCachePath = NullifyIfEmpty(
     229             :                  CPLGetConfigOption("GDAL_DEFAULT_WMS_CACHE_PATH", nullptr)))
     230             :     {
     231          90 :         m_osCachePath = pszUserCachePath;
     232             :     }
     233          11 :     else if (const char *pszXDG_CACHE_HOME =
     234          11 :                  NullifyIfEmpty(CPLGetConfigOption("XDG_CACHE_HOME", nullptr)))
     235             :     {
     236             :         m_osCachePath =
     237           1 :             CPLFormFilename(pszXDG_CACHE_HOME, "gdalwmscache", nullptr);
     238             :     }
     239             :     else
     240             :     {
     241             : #ifdef _WIN32
     242             :         const char *pszHome =
     243             :             NullifyIfEmpty(CPLGetConfigOption("USERPROFILE", nullptr));
     244             : #else
     245             :         const char *pszHome =
     246          10 :             NullifyIfEmpty(CPLGetConfigOption("HOME", nullptr));
     247             : #endif
     248          10 :         if (pszHome)
     249             :         {
     250             :             m_osCachePath =
     251             :                 CPLFormFilename(CPLFormFilename(pszHome, ".cache", nullptr),
     252           5 :                                 "gdalwmscache", nullptr);
     253             :         }
     254             :         else
     255             :         {
     256             :             const char *pszDir =
     257           5 :                 NullifyIfEmpty(CPLGetConfigOption("CPL_TMPDIR", nullptr));
     258             : 
     259           5 :             if (!pszDir)
     260           3 :                 pszDir = NullifyIfEmpty(CPLGetConfigOption("TMPDIR", nullptr));
     261             : 
     262           5 :             if (!pszDir)
     263           2 :                 pszDir = NullifyIfEmpty(CPLGetConfigOption("TEMP", nullptr));
     264             : 
     265           5 :             if (!pszDir)
     266           1 :                 pszDir = ".";
     267             : 
     268             :             const char *pszUsername =
     269           5 :                 NullifyIfEmpty(CPLGetConfigOption("USERNAME", nullptr));
     270           5 :             if (!pszUsername)
     271             :                 pszUsername =
     272           4 :                     NullifyIfEmpty(CPLGetConfigOption("USER", nullptr));
     273             : 
     274           5 :             if (pszUsername)
     275             :             {
     276             :                 m_osCachePath = CPLFormFilename(
     277             :                     pszDir, CPLSPrintf("gdalwmscache_%s", pszUsername),
     278           4 :                     nullptr);
     279             :             }
     280             :             else
     281             :             {
     282             :                 m_osCachePath = CPLFormFilename(
     283             :                     pszDir,
     284             :                     CPLSPrintf("gdalwmscache_%s",
     285             :                                CPLMD5String(pszUrl ? pszUrl : "")),
     286           1 :                     nullptr);
     287             :             }
     288             :         }
     289             :     }
     290             : 
     291             :     // Separate folder for each unique dataset url
     292         213 :     if (CPLTestBool(CPLGetXMLValue(pConfig, "Unique", "True")))
     293             :     {
     294             :         m_osCachePath = CPLFormFilename(
     295         105 :             m_osCachePath, CPLMD5String(pszUrl ? pszUrl : ""), nullptr);
     296             :     }
     297         213 :     CPLDebug("WMS", "Using %s for cache", m_osCachePath.c_str());
     298             : 
     299             :     // TODO: Add sqlite db cache type
     300         213 :     const char *pszType = CPLGetXMLValue(pConfig, "Type", "file");
     301         213 :     if (EQUAL(pszType, "file"))
     302             :     {
     303         213 :         m_poCache = new GDALWMSFileCache(m_osCachePath, pConfig);
     304             :     }
     305             : 
     306         213 :     return CE_None;
     307             : }
     308             : 
     309          18 : CPLErr GDALWMSCache::Insert(const char *pszKey, const CPLString &soFileName)
     310             : {
     311          18 :     if (m_poCache != nullptr && pszKey != nullptr)
     312             :     {
     313             :         // Add file to cache
     314          18 :         CPLErr result = m_poCache->Insert(pszKey, soFileName);
     315          18 :         if (result == CE_None)
     316             :         {
     317             :             // Start clean thread
     318          18 :             int cleanThreadRunTimeout = m_poCache->GetCleanThreadRunTimeout();
     319          36 :             if (cleanThreadRunTimeout > 0 && !m_bIsCleanThreadRunning &&
     320          18 :                 time(nullptr) - m_nCleanThreadLastRunTime >
     321          18 :                     cleanThreadRunTimeout)
     322             :             {
     323          12 :                 if (m_hThread)
     324           0 :                     CPLJoinThread(m_hThread);
     325          12 :                 m_bIsCleanThreadRunning = true;
     326          12 :                 m_hThread = CPLCreateJoinableThread(CleanCacheThread, this);
     327             :             }
     328             :         }
     329          18 :         return result;
     330             :     }
     331             : 
     332           0 :     return CE_Failure;
     333             : }
     334             : 
     335             : enum GDALWMSCacheItemStatus
     336          32 : GDALWMSCache::GetItemStatus(const char *pszKey) const
     337             : {
     338          32 :     if (m_poCache != nullptr)
     339             :     {
     340          32 :         return m_poCache->GetItemStatus(pszKey);
     341             :     }
     342           0 :     return CACHE_ITEM_NOT_FOUND;
     343             : }
     344             : 
     345          14 : GDALDataset *GDALWMSCache::GetDataset(const char *pszKey,
     346             :                                       char **papszOpenOptions) const
     347             : {
     348          14 :     if (m_poCache != nullptr)
     349             :     {
     350          14 :         return m_poCache->GetDataset(pszKey, papszOpenOptions);
     351             :     }
     352           0 :     return nullptr;
     353             : }
     354             : 
     355          12 : void GDALWMSCache::Clean()
     356             : {
     357          12 :     if (m_poCache != nullptr)
     358             :     {
     359          12 :         CPLDebug("WMS", "Clean cache");
     360          12 :         m_poCache->Clean();
     361             :     }
     362             : 
     363          12 :     m_nCleanThreadLastRunTime = time(nullptr);
     364          12 :     m_bIsCleanThreadRunning = false;
     365          12 : }

Generated by: LCOV version 1.14