Added code to automatically clean up avatar cache. There is a time limit (how long avatars are kept) as well as a number limit (how many avatars will be cached).
This commit is contained in:
@@ -24,7 +24,9 @@
|
||||
#include <playerdata.h>
|
||||
#include <net/netpacket.h>
|
||||
#include <core/crypthelper.h>
|
||||
#include <ctime>
|
||||
#include <map>
|
||||
#include <list>
|
||||
|
||||
#include <boost/thread.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
@@ -32,6 +34,14 @@
|
||||
#define MIN_AVATAR_FILE_SIZE 32
|
||||
#define MAX_AVATAR_FILE_SIZE 30720
|
||||
|
||||
#ifdef POKERTH_DEDICATED_SERVER
|
||||
#define MAX_NUMBER_OF_FILES 1024
|
||||
#define MAX_AVATAR_CACHE_AGE 2592000 // 1 Month
|
||||
#else
|
||||
#define MAX_NUMBER_OF_FILES 256
|
||||
#define MAX_AVATAR_CACHE_AGE 2592000
|
||||
#endif
|
||||
|
||||
struct AvatarFileState;
|
||||
|
||||
class AvatarManager
|
||||
@@ -55,8 +65,12 @@ public:
|
||||
|
||||
static bool IsValidAvatarFileType(AvatarFileType avatarFileType, const unsigned char *fileHeader, unsigned fileHeaderSize);
|
||||
|
||||
void RemoveOldAvatarCacheEntries();
|
||||
|
||||
protected:
|
||||
typedef std::map<MD5Buf, std::string> AvatarMap;
|
||||
typedef std::list<MD5Buf> AvatarList;
|
||||
typedef std::map<std::time_t, MD5Buf> TimeAvatarMap;
|
||||
|
||||
bool InternalReadDirectory(const std::string &dir, AvatarMap &avatars);
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <boost/lambda/lambda.hpp>
|
||||
#include <openssl/md5.h>
|
||||
#include <net/socket_msg.h>
|
||||
#include <core/loghelper.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
@@ -64,22 +65,28 @@ AvatarManager::Init(const std::string &dataDir, const std::string &cacheDir)
|
||||
{
|
||||
bool retVal = true;
|
||||
bool tmpRet;
|
||||
path tmpCachePath(cacheDir);
|
||||
path tmpDataPath(dataDir);
|
||||
{
|
||||
boost::mutex::scoped_lock lock(m_cacheDirMutex);
|
||||
m_cacheDir = cacheDir;
|
||||
m_cacheDir = tmpCachePath.directory_string();
|
||||
}
|
||||
{
|
||||
boost::mutex::scoped_lock lock(m_avatarsMutex);
|
||||
tmpRet = InternalReadDirectory(dataDir + "gfx/avatars/default/people/", m_avatars);
|
||||
tmpRet = InternalReadDirectory((tmpDataPath / "gfx/avatars/default/people/").directory_string(), m_avatars);
|
||||
retVal = retVal && tmpRet;
|
||||
tmpRet = InternalReadDirectory(dataDir + "gfx/avatars/default/misc/", m_avatars);
|
||||
tmpRet = InternalReadDirectory((tmpDataPath / "gfx/avatars/default/misc/").directory_string(), m_avatars);
|
||||
retVal = retVal && tmpRet;
|
||||
}
|
||||
if (cacheDir.empty() || tmpCachePath.empty())
|
||||
LOG_ERROR("Cache directory was not set!");
|
||||
else
|
||||
{
|
||||
boost::mutex::scoped_lock lock(m_cachedAvatarsMutex);
|
||||
tmpRet = InternalReadDirectory(cacheDir, m_cachedAvatars);
|
||||
tmpRet = InternalReadDirectory(tmpCachePath.directory_string(), m_cachedAvatars);
|
||||
retVal = retVal && tmpRet;
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@@ -125,6 +132,7 @@ AvatarManager::OpenAvatarFileForChunkRead(const std::string &fileName, unsigned
|
||||
}
|
||||
} catch (...)
|
||||
{
|
||||
LOG_ERROR("Exception caught when trying to open avatar.");
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
@@ -144,6 +152,7 @@ AvatarManager::ChunkReadAvatarFile(boost::shared_ptr<AvatarFileState> fileState,
|
||||
}
|
||||
} catch (...)
|
||||
{
|
||||
LOG_ERROR("Exception caught when trying to read avatar.");
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
@@ -286,6 +295,11 @@ bool
|
||||
AvatarManager::StoreAvatarInCache(const MD5Buf &md5buf, AvatarFileType avatarFileType, const unsigned char *data, unsigned size)
|
||||
{
|
||||
bool retVal = false;
|
||||
string cacheDir;
|
||||
{
|
||||
boost::mutex::scoped_lock lock(m_cacheDirMutex);
|
||||
cacheDir = m_cacheDir;
|
||||
}
|
||||
try
|
||||
{
|
||||
string ext;
|
||||
@@ -303,25 +317,29 @@ AvatarManager::StoreAvatarInCache(const MD5Buf &md5buf, AvatarFileType avatarFil
|
||||
case AVATAR_FILE_TYPE_UNKNOWN:
|
||||
break;
|
||||
}
|
||||
if (!ext.empty())
|
||||
if (!ext.empty() && !cacheDir.empty())
|
||||
{
|
||||
// Check header before storing file.
|
||||
if (IsValidAvatarFileType(avatarFileType, data, size))
|
||||
{
|
||||
path tmpPath(m_cacheDir);
|
||||
path tmpPath(cacheDir);
|
||||
tmpPath /= (md5buf.ToString() + ext);
|
||||
string fileName(tmpPath.file_string());
|
||||
ofstream o(fileName.c_str(), ios_base::out | ios_base::binary);
|
||||
o.write((const char *)data, size);
|
||||
if (!o.fail())
|
||||
{
|
||||
boost::mutex::scoped_lock lock(m_cachedAvatarsMutex);
|
||||
m_cachedAvatars.insert(AvatarMap::value_type(md5buf, fileName));
|
||||
o.write((const char *)data, size);
|
||||
{
|
||||
boost::mutex::scoped_lock lock(m_cachedAvatarsMutex);
|
||||
m_cachedAvatars.insert(AvatarMap::value_type(md5buf, fileName));
|
||||
}
|
||||
retVal = true;
|
||||
}
|
||||
retVal = true;
|
||||
}
|
||||
}
|
||||
} catch (...)
|
||||
{
|
||||
LOG_ERROR("Exception caught when trying to store avatar.");
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
@@ -361,6 +379,111 @@ AvatarManager::IsValidAvatarFileType(AvatarFileType avatarFileType, const unsign
|
||||
return validType;
|
||||
}
|
||||
|
||||
void
|
||||
AvatarManager::RemoveOldAvatarCacheEntries()
|
||||
{
|
||||
string cacheDir;
|
||||
{
|
||||
boost::mutex::scoped_lock lock(m_cacheDirMutex);
|
||||
cacheDir = m_cacheDir;
|
||||
}
|
||||
try
|
||||
{
|
||||
path cachePath(cacheDir);
|
||||
cacheDir = cachePath.directory_string();
|
||||
// Never delete anything if we do not have a special cache dir set.
|
||||
if (!cacheDir.empty())
|
||||
{
|
||||
boost::mutex::scoped_lock lock(m_cachedAvatarsMutex);
|
||||
|
||||
// First pass: Remove files which no longer exist.
|
||||
// Count files and record age.
|
||||
AvatarList removeList;
|
||||
TimeAvatarMap timeMap;
|
||||
unsigned fileCount = 0;
|
||||
{
|
||||
AvatarMap::const_iterator i = m_cachedAvatars.begin();
|
||||
AvatarMap::const_iterator end = m_cachedAvatars.end();
|
||||
while (i != end)
|
||||
{
|
||||
bool keepFile = false;
|
||||
path filePath(i->second);
|
||||
string fileString(filePath.file_string());
|
||||
// Only consider files which are definitely in the cache dir.
|
||||
if (fileString.size() > cacheDir.size() && fileString.substr(0, cacheDir.size()) == cacheDir)
|
||||
{
|
||||
// Only consider files with MD5 as file name.
|
||||
MD5Buf tmpBuf;
|
||||
if (exists(filePath) && tmpBuf.FromString(basename(filePath)))
|
||||
{
|
||||
++fileCount;
|
||||
timeMap.insert(TimeAvatarMap::value_type(last_write_time(filePath), i->first));
|
||||
keepFile = true;
|
||||
}
|
||||
}
|
||||
if (!keepFile)
|
||||
removeList.push_back(i->first);
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
AvatarList::const_iterator i = removeList.begin();
|
||||
AvatarList::const_iterator end = removeList.end();
|
||||
while (i != end)
|
||||
{
|
||||
m_cachedAvatars.erase(*i);
|
||||
++i;
|
||||
}
|
||||
removeList.clear();
|
||||
}
|
||||
|
||||
// Remove and physically delete files in one of the
|
||||
// following cases:
|
||||
// 1. More than MAX_NUMBER_OF_FILES files are present
|
||||
// - delete until only MAX_NUMBER_OF_FILES/2 are left.
|
||||
// 2. Files are older than 30 days.
|
||||
|
||||
if (m_cachedAvatars.size() > MAX_NUMBER_OF_FILES)
|
||||
{
|
||||
while (!timeMap.empty() && m_cachedAvatars.size() > MAX_NUMBER_OF_FILES / 2)
|
||||
{
|
||||
TimeAvatarMap::iterator i = timeMap.begin();
|
||||
AvatarMap::iterator pos = m_cachedAvatars.find(i->second);
|
||||
if (pos != m_cachedAvatars.end())
|
||||
{
|
||||
path tmpPath(pos->second);
|
||||
remove(tmpPath);
|
||||
m_cachedAvatars.erase(pos);
|
||||
}
|
||||
timeMap.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Get reference time.
|
||||
time_t curTime = time(NULL);
|
||||
while (!timeMap.empty() && !m_cachedAvatars.empty())
|
||||
{
|
||||
TimeAvatarMap::iterator i = timeMap.begin();
|
||||
if (curTime - i->first < MAX_AVATAR_CACHE_AGE)
|
||||
break;
|
||||
AvatarMap::iterator pos = m_cachedAvatars.find(i->second);
|
||||
if (pos != m_cachedAvatars.end())
|
||||
{
|
||||
path tmpPath(pos->second);
|
||||
remove(tmpPath);
|
||||
m_cachedAvatars.erase(pos);
|
||||
}
|
||||
timeMap.erase(i);
|
||||
}
|
||||
}
|
||||
} catch (...)
|
||||
{
|
||||
LOG_ERROR("Exception caught while cleaning up cache.");
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
AvatarManager::InternalReadDirectory(const std::string &dir, AvatarMap &avatars)
|
||||
{
|
||||
@@ -388,6 +511,7 @@ AvatarManager::InternalReadDirectory(const std::string &dir, AvatarMap &avatars)
|
||||
}
|
||||
} catch (...)
|
||||
{
|
||||
LOG_ERROR("Exception caught when trying to scan cache directory.");
|
||||
retVal = false;
|
||||
}
|
||||
return retVal;
|
||||
|
||||
+3
-1
@@ -56,7 +56,9 @@ Session::~Session()
|
||||
bool Session::init()
|
||||
{
|
||||
myAvatarManager.reset(new AvatarManager);
|
||||
return myAvatarManager->Init(myConfig->readConfigString("AppDataDir"), myConfig->readConfigString("CacheDir"));
|
||||
bool retVal = myAvatarManager->Init(myConfig->readConfigString("AppDataDir"), myConfig->readConfigString("CacheDir"));
|
||||
myAvatarManager->RemoveOldAvatarCacheEntries();
|
||||
return retVal;
|
||||
}
|
||||
|
||||
void Session::init(boost::shared_ptr<AvatarManager> manager)
|
||||
|
||||
Reference in New Issue
Block a user