gdal/frmts/wms/gdalwmscache.cpp

326 строки
10 KiB
C++

/******************************************************************************
*
* Project: WMS Client Driver
* Purpose: Implementation of Dataset and RasterBand classes for WMS
* and other similar services.
* Author: Adam Nowacki, nowak@xpam.de
*
******************************************************************************
* Copyright (c) 2007, Adam Nowacki
* Copyright (c) 2017, Dmitry Baryshnikov, <polimax@mail.ru>
* Copyright (c) 2017, NextGIS, <info@nextgis.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
****************************************************************************/
#include "cpl_md5.h"
#include "wmsdriver.h"
static void CleanCacheThread(void *pData)
{
GDALWMSCache *pCache = static_cast<GDALWMSCache *>(pData);
pCache->Clean();
}
//------------------------------------------------------------------------------
// GDALWMSFileCache
//------------------------------------------------------------------------------
class GDALWMSFileCache : public GDALWMSCacheImpl
{
public:
GDALWMSFileCache(const CPLString &soPath, CPLXMLNode *pConfig)
: GDALWMSCacheImpl(soPath, pConfig), m_osPostfix(""), m_nDepth(2),
m_nExpires(604800), // 7 days
m_nMaxSize(67108864), // 64 Mb
m_nCleanThreadRunTimeout(120) // 3 min
{
const char *pszCacheDepth = CPLGetXMLValue(pConfig, "Depth", "2");
if (pszCacheDepth != nullptr)
m_nDepth = atoi(pszCacheDepth);
const char *pszCacheExtension =
CPLGetXMLValue(pConfig, "Extension", nullptr);
if (pszCacheExtension != nullptr)
m_osPostfix = pszCacheExtension;
const char *pszCacheExpires =
CPLGetXMLValue(pConfig, "Expires", nullptr);
if (pszCacheExpires != nullptr)
{
m_nExpires = atoi(pszCacheExpires);
CPLDebug("WMS", "Cache expires in %d sec", m_nExpires);
}
const char *pszCacheMaxSize =
CPLGetXMLValue(pConfig, "MaxSize", nullptr);
if (pszCacheMaxSize != nullptr)
m_nMaxSize = atol(pszCacheMaxSize);
const char *pszCleanThreadRunTimeout =
CPLGetXMLValue(pConfig, "CleanTimeout", nullptr);
if (pszCleanThreadRunTimeout != nullptr)
{
m_nCleanThreadRunTimeout = atoi(pszCleanThreadRunTimeout);
CPLDebug("WMS", "Clean Thread Run Timeout is %d sec",
m_nCleanThreadRunTimeout);
}
}
virtual int GetCleanThreadRunTimeout() override
{
return m_nCleanThreadRunTimeout;
}
virtual CPLErr Insert(const char *pszKey,
const CPLString &osFileName) override
{
// Warns if it fails to write, but returns success
CPLString soFilePath = GetFilePath(pszKey);
MakeDirs(CPLGetDirname(soFilePath));
if (CPLCopyFile(soFilePath, osFileName) == CE_None)
return CE_None;
// Warn if it fails after folder creation
CPLError(CE_Warning, CPLE_FileIO, "Error writing to WMS cache %s",
m_soPath.c_str());
return CE_None;
}
virtual enum GDALWMSCacheItemStatus
GetItemStatus(const char *pszKey) const override
{
VSIStatBufL sStatBuf;
if (VSIStatL(GetFilePath(pszKey), &sStatBuf) == 0)
{
long seconds = static_cast<long>(time(nullptr) - sStatBuf.st_mtime);
return seconds < m_nExpires ? CACHE_ITEM_OK : CACHE_ITEM_EXPIRED;
}
return CACHE_ITEM_NOT_FOUND;
}
virtual GDALDataset *GetDataset(const char *pszKey,
char **papszOpenOptions) const override
{
return GDALDataset::FromHandle(GDALOpenEx(
GetFilePath(pszKey),
GDAL_OF_RASTER | GDAL_OF_READONLY | GDAL_OF_VERBOSE_ERROR, nullptr,
papszOpenOptions, nullptr));
}
virtual void Clean() override
{
char **papszList = VSIReadDirRecursive(m_soPath);
if (papszList == nullptr)
{
return;
}
int counter = 0;
std::vector<int> toDelete;
long nSize = 0;
time_t nTime = time(nullptr);
while (papszList[counter] != nullptr)
{
const char *pszPath =
CPLFormFilename(m_soPath, papszList[counter], nullptr);
VSIStatBufL sStatBuf;
if (VSIStatL(pszPath, &sStatBuf) == 0)
{
if (!VSI_ISDIR(sStatBuf.st_mode))
{
long seconds = static_cast<long>(nTime - sStatBuf.st_mtime);
if (seconds > m_nExpires)
{
toDelete.push_back(counter);
}
nSize += static_cast<long>(sStatBuf.st_size);
}
}
counter++;
}
if (nSize > m_nMaxSize)
{
CPLDebug("WMS", "Delete %u items from cache",
static_cast<unsigned int>(toDelete.size()));
for (size_t i = 0; i < toDelete.size(); ++i)
{
const char *pszPath =
CPLFormFilename(m_soPath, papszList[toDelete[i]], nullptr);
VSIUnlink(pszPath);
}
}
CSLDestroy(papszList);
}
private:
CPLString GetFilePath(const char *pszKey) const
{
CPLString soHash(CPLMD5String(pszKey));
CPLString soCacheFile(m_soPath);
if (!soCacheFile.empty() && soCacheFile.back() != '/')
{
soCacheFile.append(1, '/');
}
for (int i = 0; i < m_nDepth; ++i)
{
soCacheFile.append(1, soHash[i]);
soCacheFile.append(1, '/');
}
soCacheFile.append(soHash);
soCacheFile.append(m_osPostfix);
return soCacheFile;
}
static void MakeDirs(const char *pszPath)
{
if (IsPathExists(pszPath))
{
return;
}
// Recursive makedirs, ignoring errors
const char *pszDirPath = CPLGetDirname(pszPath);
MakeDirs(pszDirPath);
VSIMkdir(pszPath, 0744);
}
static bool IsPathExists(const char *pszPath)
{
VSIStatBufL sbuf;
return VSIStatL(pszPath, &sbuf) == 0;
}
private:
CPLString m_osPostfix;
int m_nDepth;
int m_nExpires;
long m_nMaxSize;
int m_nCleanThreadRunTimeout;
};
//------------------------------------------------------------------------------
// GDALWMSCache
//------------------------------------------------------------------------------
GDALWMSCache::GDALWMSCache()
: m_osCachePath("./gdalwmscache"), m_bIsCleanThreadRunning(false),
m_nCleanThreadLastRunTime(0), m_poCache(nullptr), m_hThread(nullptr)
{
}
GDALWMSCache::~GDALWMSCache()
{
if (m_hThread)
CPLJoinThread(m_hThread);
delete m_poCache;
}
CPLErr GDALWMSCache::Initialize(const char *pszUrl, CPLXMLNode *pConfig)
{
const char *pszXmlCachePath = CPLGetXMLValue(pConfig, "Path", nullptr);
const char *pszUserCachePath =
CPLGetConfigOption("GDAL_DEFAULT_WMS_CACHE_PATH", nullptr);
if (pszXmlCachePath != nullptr)
{
m_osCachePath = pszXmlCachePath;
}
else if (pszUserCachePath != nullptr)
{
m_osCachePath = pszUserCachePath;
}
// Separate folder for each unique dataset url
if (CPLTestBool(CPLGetXMLValue(pConfig, "Unique", "True")))
{
m_osCachePath =
CPLFormFilename(m_osCachePath, CPLMD5String(pszUrl), nullptr);
}
// TODO: Add sqlite db cache type
const char *pszType = CPLGetXMLValue(pConfig, "Type", "file");
if (EQUAL(pszType, "file"))
{
m_poCache = new GDALWMSFileCache(m_osCachePath, pConfig);
}
return CE_None;
}
CPLErr GDALWMSCache::Insert(const char *pszKey, const CPLString &soFileName)
{
if (m_poCache != nullptr && pszKey != nullptr)
{
// Add file to cache
CPLErr result = m_poCache->Insert(pszKey, soFileName);
if (result == CE_None)
{
// Start clean thread
int cleanThreadRunTimeout = m_poCache->GetCleanThreadRunTimeout();
if (cleanThreadRunTimeout > 0 && !m_bIsCleanThreadRunning &&
time(nullptr) - m_nCleanThreadLastRunTime >
cleanThreadRunTimeout)
{
if (m_hThread)
CPLJoinThread(m_hThread);
m_bIsCleanThreadRunning = true;
m_hThread = CPLCreateJoinableThread(CleanCacheThread, this);
}
}
return result;
}
return CE_Failure;
}
enum GDALWMSCacheItemStatus
GDALWMSCache::GetItemStatus(const char *pszKey) const
{
if (m_poCache != nullptr)
{
return m_poCache->GetItemStatus(pszKey);
}
return CACHE_ITEM_NOT_FOUND;
}
GDALDataset *GDALWMSCache::GetDataset(const char *pszKey,
char **papszOpenOptions) const
{
if (m_poCache != nullptr)
{
return m_poCache->GetDataset(pszKey, papszOpenOptions);
}
return nullptr;
}
void GDALWMSCache::Clean()
{
if (m_poCache != nullptr)
{
CPLDebug("WMS", "Clean cache");
m_poCache->Clean();
}
m_nCleanThreadLastRunTime = time(nullptr);
m_bIsCleanThreadRunning = false;
}