2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
21 #include "TextureCache.h"
22 #include "TextureCacheJob.h"
23 #include "filesystem/File.h"
24 #include "profiles/ProfilesManager.h"
25 #include "threads/SingleLock.h"
26 #include "utils/Crc32.h"
27 #include "settings/AdvancedSettings.h"
28 #include "utils/log.h"
29 #include "utils/URIUtils.h"
30 #include "utils/StringUtils.h"
32 #include "utils/StringUtils.h"
34 using namespace XFILE;
36 CTextureCache &CTextureCache::Get()
38 static CTextureCache s_cache;
42 CTextureCache::CTextureCache() : CJobQueue(false, 1, CJob::PRIORITY_LOW_PAUSABLE)
46 CTextureCache::~CTextureCache()
50 void CTextureCache::Initialize()
52 CSingleLock lock(m_databaseSection);
53 if (!m_database.IsOpen())
57 void CTextureCache::Deinitialize()
60 CSingleLock lock(m_databaseSection);
64 bool CTextureCache::IsCachedImage(const CStdString &url) const
66 if (url != "-" && !CURL::IsFullPath(url))
68 if (URIUtils::IsInPath(url, "special://skin/") ||
69 URIUtils::IsInPath(url, "androidapp://") ||
70 URIUtils::IsInPath(url, CProfilesManager::Get().GetThumbnailsFolder()))
75 bool CTextureCache::HasCachedImage(const CStdString &url)
77 CTextureDetails details;
78 CStdString cachedImage(GetCachedImage(url, details));
79 return (!cachedImage.empty() && cachedImage != url);
82 CStdString CTextureCache::GetCachedImage(const CStdString &image, CTextureDetails &details, bool trackUsage)
84 CStdString url = CTextureUtils::UnwrapImageURL(image);
86 if (IsCachedImage(url))
89 // lookup the item in the database
90 if (GetCachedTexture(url, details))
93 IncrementUseCount(details);
94 return GetCachedPath(details.file);
99 bool CTextureCache::CanCacheImageURL(const CURL &url)
101 return (url.GetUserName().empty() || url.GetUserName() == "music");
104 CStdString CTextureCache::CheckCachedImage(const CStdString &url, bool returnDDS, bool &needsRecaching)
106 CTextureDetails details;
107 CStdString path(GetCachedImage(url, details, true));
108 needsRecaching = !details.hash.empty();
111 if (!needsRecaching && returnDDS && !URIUtils::IsInPath(url, "special://skin/")) // TODO: should skin images be .dds'd (currently they're not necessarily writeable)
112 { // check for dds version
113 CStdString ddsPath = URIUtils::ReplaceExtension(path, ".dds");
114 if (CFile::Exists(ddsPath))
116 if (g_advancedSettings.m_useDDSFanart)
117 AddJob(new CTextureDDSJob(path));
124 void CTextureCache::BackgroundCacheImage(const CStdString &url)
126 CTextureDetails details;
127 CStdString path(GetCachedImage(url, details));
128 if (!path.empty() && details.hash.empty())
129 return; // image is already cached and doesn't need to be checked further
132 AddJob(new CTextureCacheJob(CTextureUtils::UnwrapImageURL(url), details.hash));
135 bool CTextureCache::CacheImage(const CStdString &image, CTextureDetails &details)
137 CStdString path = GetCachedImage(image, details);
138 if (path.empty()) // not cached
139 path = CacheImage(image, NULL, &details);
141 return !path.empty();
144 CStdString CTextureCache::CacheImage(const CStdString &image, CBaseTexture **texture, CTextureDetails *details)
146 CStdString url = CTextureUtils::UnwrapImageURL(image);
147 CSingleLock lock(m_processingSection);
148 if (m_processing.find(url) == m_processing.end())
150 m_processing.insert(url);
152 // cache the texture directly
153 CTextureCacheJob job(url);
154 bool success = job.CacheTexture(texture);
155 OnCachingComplete(success, &job);
156 if (success && details)
157 *details = job.m_details;
158 return success ? GetCachedPath(job.m_details.file) : "";
162 // wait for currently processing job to end.
165 m_completeEvent.WaitMSec(1000);
167 CSingleLock lock(m_processingSection);
168 if (m_processing.find(url) == m_processing.end())
172 CTextureDetails tempDetails;
174 details = &tempDetails;
175 return GetCachedImage(url, *details, true);
178 void CTextureCache::ClearCachedImage(const CStdString &url, bool deleteSource /*= false */)
180 // TODO: This can be removed when the texture cache covers everything.
181 CStdString path = deleteSource ? url : "";
182 CStdString cachedFile;
183 if (ClearCachedTexture(url, cachedFile))
184 path = GetCachedPath(cachedFile);
185 if (CFile::Exists(path))
187 path = URIUtils::ReplaceExtension(path, ".dds");
188 if (CFile::Exists(path))
192 bool CTextureCache::ClearCachedImage(int id)
194 CStdString cachedFile;
195 if (ClearCachedTexture(id, cachedFile))
197 cachedFile = GetCachedPath(cachedFile);
198 if (CFile::Exists(cachedFile))
199 CFile::Delete(cachedFile);
200 cachedFile = URIUtils::ReplaceExtension(cachedFile, ".dds");
201 if (CFile::Exists(cachedFile))
202 CFile::Delete(cachedFile);
208 bool CTextureCache::GetCachedTexture(const CStdString &url, CTextureDetails &details)
210 CSingleLock lock(m_databaseSection);
211 return m_database.GetCachedTexture(url, details);
214 bool CTextureCache::AddCachedTexture(const CStdString &url, const CTextureDetails &details)
216 CSingleLock lock(m_databaseSection);
217 return m_database.AddCachedTexture(url, details);
220 void CTextureCache::IncrementUseCount(const CTextureDetails &details)
222 static const size_t count_before_update = 100;
223 CSingleLock lock(m_useCountSection);
224 m_useCounts.reserve(count_before_update);
225 m_useCounts.push_back(details);
226 if (m_useCounts.size() >= count_before_update)
228 AddJob(new CTextureUseCountJob(m_useCounts));
233 bool CTextureCache::SetCachedTextureValid(const CStdString &url, bool updateable)
235 CSingleLock lock(m_databaseSection);
236 return m_database.SetCachedTextureValid(url, updateable);
239 bool CTextureCache::ClearCachedTexture(const CStdString &url, CStdString &cachedURL)
241 CSingleLock lock(m_databaseSection);
242 return m_database.ClearCachedTexture(url, cachedURL);
245 bool CTextureCache::ClearCachedTexture(int id, CStdString &cachedURL)
247 CSingleLock lock(m_databaseSection);
248 return m_database.ClearCachedTexture(id, cachedURL);
251 CStdString CTextureCache::GetCacheFile(const CStdString &url)
254 crc.ComputeFromLowerCase(url);
255 CStdString hex = StringUtils::Format("%08x", (unsigned int)crc);
256 CStdString hash = StringUtils::Format("%c/%s", hex[0], hex.c_str());
260 CStdString CTextureCache::GetCachedPath(const CStdString &file)
262 return URIUtils::AddFileToFolder(CProfilesManager::Get().GetThumbnailsFolder(), file);
265 void CTextureCache::OnCachingComplete(bool success, CTextureCacheJob *job)
269 if (job->m_oldHash == job->m_details.hash)
270 SetCachedTextureValid(job->m_url, job->m_details.updateable);
272 AddCachedTexture(job->m_url, job->m_details);
275 { // remove from our processing list
276 CSingleLock lock(m_processingSection);
277 std::set<CStdString>::iterator i = m_processing.find(job->m_url);
278 if (i != m_processing.end())
279 m_processing.erase(i);
282 m_completeEvent.Set();
284 // TODO: call back to the UI indicating that it can update it's image...
285 if (success && g_advancedSettings.m_useDDSFanart && !job->m_details.file.empty())
286 AddJob(new CTextureDDSJob(GetCachedPath(job->m_details.file)));
289 void CTextureCache::OnJobComplete(unsigned int jobID, bool success, CJob *job)
291 if (strcmp(job->GetType(), kJobTypeCacheImage) == 0)
292 OnCachingComplete(success, (CTextureCacheJob *)job);
293 return CJobQueue::OnJobComplete(jobID, success, job);
296 void CTextureCache::OnJobProgress(unsigned int jobID, unsigned int progress, unsigned int total, const CJob *job)
298 if (strcmp(job->GetType(), kJobTypeCacheImage) == 0 && !progress)
299 { // check our processing list
301 CSingleLock lock(m_processingSection);
302 const CTextureCacheJob *cacheJob = (CTextureCacheJob *)job;
303 std::set<CStdString>::iterator i = m_processing.find(cacheJob->m_url);
304 if (i == m_processing.end())
306 m_processing.insert(cacheJob->m_url);
313 CJobQueue::OnJobProgress(jobID, progress, total, job);
316 bool CTextureCache::Export(const CStdString &image, const CStdString &destination, bool overwrite)
318 CTextureDetails details;
319 CStdString cachedImage(GetCachedImage(image, details));
320 if (!cachedImage.empty())
322 CStdString dest = destination + URIUtils::GetExtension(cachedImage);
323 if (overwrite || !CFile::Exists(dest))
325 if (CFile::Cache(cachedImage, dest))
327 CLog::Log(LOGERROR, "%s failed exporting '%s' to '%s'", __FUNCTION__, cachedImage.c_str(), dest.c_str());
333 bool CTextureCache::Export(const CStdString &image, const CStdString &destination)
335 CTextureDetails details;
336 CStdString cachedImage(GetCachedImage(image, details));
337 if (!cachedImage.empty())
339 if (CFile::Cache(cachedImage, destination))
341 CLog::Log(LOGERROR, "%s failed exporting '%s' to '%s'", __FUNCTION__, cachedImage.c_str(), destination.c_str());