Files
xbmc4xbox-redux/xbmc/TextureCache.cpp
antonic901 e4e594bfd7 [Texture Caching] backported from Kodi v17 Krypton without some features:
- removed support for resizing images
- removed support for scaling algorithms
- re-added support for 'pictures.useexifrotation'
- removed mimetype and requirePixels since our CBaseTexture doesn't need that for now
2024-07-30 01:31:27 +02:00

342 lines
9.6 KiB
C++

/*
* Copyright (C) 2005-2013 Team XBMC
* http://xbmc.org
*
* This Program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This Program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with XBMC; see the file COPYING. If not, see
* <http://www.gnu.org/licenses/>.
*
*/
#include "TextureCache.h"
#include "TextureCacheJob.h"
#include "filesystem/File.h"
#include "profiles/ProfilesManager.h"
#include "threads/SingleLock.h"
#include "utils/Crc32.h"
#include "settings/AdvancedSettings.h"
#include "utils/log.h"
#include "utils/URIUtils.h"
#include "utils/StringUtils.h"
#include "URL.h"
using namespace XFILE;
CTextureCache &CTextureCache::Get()
{
static CTextureCache s_cache;
return s_cache;
}
CTextureCache::CTextureCache() : CJobQueue(false, 1, CJob::PRIORITY_LOW_PAUSABLE)
{
}
CTextureCache::~CTextureCache()
{
}
void CTextureCache::Initialize()
{
CSingleLock lock(m_databaseSection);
if (!m_database.IsOpen())
m_database.Open();
}
void CTextureCache::Deinitialize()
{
CancelJobs();
CSingleLock lock(m_databaseSection);
m_database.Close();
}
bool CTextureCache::IsCachedImage(const std::string &url) const
{
if (url != "-" && !CURL::IsFullPath(url))
return true;
if (URIUtils::PathHasParent(url, "special://skin", true) ||
URIUtils::PathHasParent(url, "special://temp", true) ||
URIUtils::PathHasParent(url, "resource://", true) ||
URIUtils::PathHasParent(url, "androidapp://", true) ||
URIUtils::PathHasParent(url, CProfilesManager::Get().GetThumbnailsFolder(), true))
return true;
return false;
}
bool CTextureCache::HasCachedImage(const std::string &url)
{
CTextureDetails details;
std::string cachedImage(GetCachedImage(url, details));
return (!cachedImage.empty() && cachedImage != url);
}
std::string CTextureCache::GetCachedImage(const std::string &image, CTextureDetails &details, bool trackUsage)
{
std::string url = CTextureUtils::UnwrapImageURL(image);
if (IsCachedImage(url))
return url;
// lookup the item in the database
if (GetCachedTexture(url, details))
{
if (trackUsage)
IncrementUseCount(details);
return GetCachedPath(details.file);
}
return "";
}
bool CTextureCache::CanCacheImageURL(const CURL &url)
{
return (url.GetUserName().empty() || url.GetUserName() == "music");
}
std::string CTextureCache::CheckCachedImage(const std::string &url, bool &needsRecaching)
{
CTextureDetails details;
std::string path(GetCachedImage(url, details, true));
needsRecaching = !details.hash.empty();
if (!path.empty())
return path;
return "";
}
void CTextureCache::BackgroundCacheImage(const std::string &url)
{
if (url.empty())
return;
CTextureDetails details;
std::string path(GetCachedImage(url, details));
if (!path.empty() && details.hash.empty())
return; // image is already cached and doesn't need to be checked further
path = CTextureUtils::UnwrapImageURL(url);
if (path.empty())
return;
// needs (re)caching
AddJob(new CTextureCacheJob(path, details.hash));
}
std::string CTextureCache::CacheImage(const std::string &image, CBaseTexture **texture /* = NULL */, CTextureDetails *details /* = NULL */)
{
std::string url = CTextureUtils::UnwrapImageURL(image);
if (url.empty())
return "";
CSingleLock lock(m_processingSection);
if (m_processinglist.find(url) == m_processinglist.end())
{
m_processinglist.insert(url);
lock.Leave();
// cache the texture directly
CTextureCacheJob job(url);
bool success = job.CacheTexture(texture);
OnCachingComplete(success, &job);
if (success && details)
*details = job.m_details;
return success ? GetCachedPath(job.m_details.file) : "";
}
lock.Leave();
// wait for currently processing job to end.
while (true)
{
m_completeEvent.WaitMSec(1000);
{
CSingleLock lock(m_processingSection);
if (m_processinglist.find(url) == m_processinglist.end())
break;
}
}
CTextureDetails tempDetails;
if (!details)
details = &tempDetails;
return GetCachedImage(url, *details, true);
}
bool CTextureCache::CacheImage(const std::string &image, CTextureDetails &details)
{
std::string path = GetCachedImage(image, details);
if (path.empty()) // not cached
path = CacheImage(image, NULL, &details);
return !path.empty();
}
void CTextureCache::ClearCachedImage(const std::string &url, bool deleteSource /*= false */)
{
//! @todo This can be removed when the texture cache covers everything.
std::string path = deleteSource ? url : "";
std::string cachedFile;
if (ClearCachedTexture(url, cachedFile))
path = GetCachedPath(cachedFile);
if (CFile::Exists(path))
CFile::Delete(path);
path = URIUtils::ReplaceExtension(path, ".dds");
if (CFile::Exists(path))
CFile::Delete(path);
}
bool CTextureCache::ClearCachedImage(int id)
{
std::string cachedFile;
if (ClearCachedTexture(id, cachedFile))
{
cachedFile = GetCachedPath(cachedFile);
if (CFile::Exists(cachedFile))
CFile::Delete(cachedFile);
cachedFile = URIUtils::ReplaceExtension(cachedFile, ".dds");
if (CFile::Exists(cachedFile))
CFile::Delete(cachedFile);
return true;
}
return false;
}
bool CTextureCache::GetCachedTexture(const std::string &url, CTextureDetails &details)
{
CSingleLock lock(m_databaseSection);
return m_database.GetCachedTexture(url, details);
}
bool CTextureCache::AddCachedTexture(const std::string &url, const CTextureDetails &details)
{
CSingleLock lock(m_databaseSection);
return m_database.AddCachedTexture(url, details);
}
void CTextureCache::IncrementUseCount(const CTextureDetails &details)
{
static const size_t count_before_update = 100;
CSingleLock lock(m_useCountSection);
m_useCounts.reserve(count_before_update);
m_useCounts.push_back(details);
if (m_useCounts.size() >= count_before_update)
{
AddJob(new CTextureUseCountJob(m_useCounts));
m_useCounts.clear();
}
}
bool CTextureCache::SetCachedTextureValid(const std::string &url, bool updateable)
{
CSingleLock lock(m_databaseSection);
return m_database.SetCachedTextureValid(url, updateable);
}
bool CTextureCache::ClearCachedTexture(const std::string &url, std::string &cachedURL)
{
CSingleLock lock(m_databaseSection);
return m_database.ClearCachedTexture(url, cachedURL);
}
bool CTextureCache::ClearCachedTexture(int id, std::string &cachedURL)
{
CSingleLock lock(m_databaseSection);
return m_database.ClearCachedTexture(id, cachedURL);
}
std::string CTextureCache::GetCacheFile(const std::string &url)
{
Crc32 crc;
crc.ComputeFromLowerCase(url);
std::string hex = StringUtils::Format("%08x", (unsigned int)crc);
std::string hash = StringUtils::Format("%c/%s", hex[0], hex.c_str());
return hash;
}
std::string CTextureCache::GetCachedPath(const std::string &file)
{
return URIUtils::AddFileToFolder(CProfilesManager::Get().GetThumbnailsFolder(), file);
}
void CTextureCache::OnCachingComplete(bool success, CTextureCacheJob *job)
{
if (success)
{
if (job->m_oldHash == job->m_details.hash)
SetCachedTextureValid(job->m_url, job->m_details.updateable);
else
AddCachedTexture(job->m_url, job->m_details);
}
{ // remove from our processing list
CSingleLock lock(m_processingSection);
std::set<std::string>::iterator i = m_processinglist.find(job->m_url);
if (i != m_processinglist.end())
m_processinglist.erase(i);
}
m_completeEvent.Set();
}
void CTextureCache::OnJobComplete(unsigned int jobID, bool success, CJob *job)
{
if (strcmp(job->GetType(), kJobTypeCacheImage) == 0)
OnCachingComplete(success, (CTextureCacheJob *)job);
return CJobQueue::OnJobComplete(jobID, success, job);
}
void CTextureCache::OnJobProgress(unsigned int jobID, unsigned int progress, unsigned int total, const CJob *job)
{
if (strcmp(job->GetType(), kJobTypeCacheImage) == 0 && !progress)
{ // check our processing list
{
CSingleLock lock(m_processingSection);
const CTextureCacheJob *cacheJob = (CTextureCacheJob *)job;
std::set<std::string>::iterator i = m_processinglist.find(cacheJob->m_url);
if (i == m_processinglist.end())
{
m_processinglist.insert(cacheJob->m_url);
return;
}
}
CancelJob(job);
}
else
CJobQueue::OnJobProgress(jobID, progress, total, job);
}
bool CTextureCache::Export(const std::string &image, const std::string &destination, bool overwrite)
{
CTextureDetails details;
std::string cachedImage(GetCachedImage(image, details));
if (!cachedImage.empty())
{
std::string dest = destination + URIUtils::GetExtension(cachedImage).c_str();
if (overwrite || !CFile::Exists(dest))
{
if (CFile::Copy(cachedImage, dest))
return true;
CLog::Log(LOGERROR, "%s failed exporting '%s' to '%s'", __FUNCTION__, cachedImage.c_str(), dest.c_str());
}
}
return false;
}
bool CTextureCache::Export(const std::string &image, const std::string &destination)
{
CTextureDetails details;
std::string cachedImage(GetCachedImage(image, details));
if (!cachedImage.empty())
{
if (CFile::Copy(cachedImage, destination))
return true;
CLog::Log(LOGERROR, "%s failed exporting '%s' to '%s'", __FUNCTION__, cachedImage.c_str(), destination.c_str());
}
return false;
}