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, "special://temp/") ||
70 URIUtils::IsInPath(url, "androidapp://") ||
71 URIUtils::IsInPath(url, CProfilesManager::Get().GetThumbnailsFolder()))
76 bool CTextureCache::HasCachedImage(const CStdString &url)
78 CTextureDetails details;
79 CStdString cachedImage(GetCachedImage(url, details));
80 return (!cachedImage.empty() && cachedImage != url);
83 CStdString CTextureCache::GetCachedImage(const CStdString &image, CTextureDetails &details, bool trackUsage)
85 CStdString url = CTextureUtils::UnwrapImageURL(image);
87 if (IsCachedImage(url))
90 // lookup the item in the database
91 if (GetCachedTexture(url, details))
94 IncrementUseCount(details);
95 return GetCachedPath(details.file);
100 bool CTextureCache::CanCacheImageURL(const CURL &url)
102 return (url.GetUserName().empty() || url.GetUserName() == "music");
105 CStdString CTextureCache::CheckCachedImage(const CStdString &url, bool returnDDS, bool &needsRecaching)
107 CTextureDetails details;
108 CStdString path(GetCachedImage(url, details, true));
109 needsRecaching = !details.hash.empty();
112 if (!needsRecaching && returnDDS && !URIUtils::IsInPath(url, "special://skin/")) // TODO: should skin images be .dds'd (currently they're not necessarily writeable)
113 { // check for dds version
114 CStdString ddsPath = URIUtils::ReplaceExtension(path, ".dds");
115 if (CFile::Exists(ddsPath))
117 if (g_advancedSettings.m_useDDSFanart)
118 AddJob(new CTextureDDSJob(path));
125 void CTextureCache::BackgroundCacheImage(const CStdString &url)
127 CTextureDetails details;
128 CStdString path(GetCachedImage(url, details));
129 if (!path.empty() && details.hash.empty())
130 return; // image is already cached and doesn't need to be checked further
133 AddJob(new CTextureCacheJob(CTextureUtils::UnwrapImageURL(url), details.hash));
136 bool CTextureCache::CacheImage(const CStdString &image, CTextureDetails &details)
138 CStdString path = GetCachedImage(image, details);
139 if (path.empty()) // not cached
140 path = CacheImage(image, NULL, &details);
142 return !path.empty();
145 CStdString CTextureCache::CacheImage(const CStdString &image, CBaseTexture **texture, CTextureDetails *details)
147 CStdString url = CTextureUtils::UnwrapImageURL(image);
148 CSingleLock lock(m_processingSection);
149 if (m_processinglist.find(url) == m_processinglist.end())
151 m_processinglist.insert(url);
153 // cache the texture directly
154 CTextureCacheJob job(url);
155 bool success = job.CacheTexture(texture);
156 OnCachingComplete(success, &job);
157 if (success && details)
158 *details = job.m_details;
159 return success ? GetCachedPath(job.m_details.file) : "";
163 // wait for currently processing job to end.
166 m_completeEvent.WaitMSec(1000);
168 CSingleLock lock(m_processingSection);
169 if (m_processinglist.find(url) == m_processinglist.end())
173 CTextureDetails tempDetails;
175 details = &tempDetails;
176 return GetCachedImage(url, *details, true);
179 void CTextureCache::ClearCachedImage(const CStdString &url, bool deleteSource /*= false */)
181 // TODO: This can be removed when the texture cache covers everything.
182 CStdString path = deleteSource ? url : "";
183 CStdString cachedFile;
184 if (ClearCachedTexture(url, cachedFile))
185 path = GetCachedPath(cachedFile);
186 if (CFile::Exists(path))
188 path = URIUtils::ReplaceExtension(path, ".dds");
189 if (CFile::Exists(path))
193 bool CTextureCache::ClearCachedImage(int id)
195 CStdString cachedFile;
196 if (ClearCachedTexture(id, cachedFile))
198 cachedFile = GetCachedPath(cachedFile);
199 if (CFile::Exists(cachedFile))
200 CFile::Delete(cachedFile);
201 cachedFile = URIUtils::ReplaceExtension(cachedFile, ".dds");
202 if (CFile::Exists(cachedFile))
203 CFile::Delete(cachedFile);
209 bool CTextureCache::GetCachedTexture(const CStdString &url, CTextureDetails &details)
211 CSingleLock lock(m_databaseSection);
212 return m_database.GetCachedTexture(url, details);
215 bool CTextureCache::AddCachedTexture(const CStdString &url, const CTextureDetails &details)
217 CSingleLock lock(m_databaseSection);
218 return m_database.AddCachedTexture(url, details);
221 void CTextureCache::IncrementUseCount(const CTextureDetails &details)
223 static const size_t count_before_update = 100;
224 CSingleLock lock(m_useCountSection);
225 m_useCounts.reserve(count_before_update);
226 m_useCounts.push_back(details);
227 if (m_useCounts.size() >= count_before_update)
229 AddJob(new CTextureUseCountJob(m_useCounts));
234 bool CTextureCache::SetCachedTextureValid(const CStdString &url, bool updateable)
236 CSingleLock lock(m_databaseSection);
237 return m_database.SetCachedTextureValid(url, updateable);
240 bool CTextureCache::ClearCachedTexture(const CStdString &url, CStdString &cachedURL)
242 CSingleLock lock(m_databaseSection);
243 return m_database.ClearCachedTexture(url, cachedURL);
246 bool CTextureCache::ClearCachedTexture(int id, CStdString &cachedURL)
248 CSingleLock lock(m_databaseSection);
249 return m_database.ClearCachedTexture(id, cachedURL);
252 CStdString CTextureCache::GetCacheFile(const CStdString &url)
255 crc.ComputeFromLowerCase(url);
256 CStdString hex = StringUtils::Format("%08x", (unsigned int)crc);
257 CStdString hash = StringUtils::Format("%c/%s", hex[0], hex.c_str());
261 CStdString CTextureCache::GetCachedPath(const CStdString &file)
263 return URIUtils::AddFileToFolder(CProfilesManager::Get().GetThumbnailsFolder(), file);
266 void CTextureCache::OnCachingComplete(bool success, CTextureCacheJob *job)
270 if (job->m_oldHash == job->m_details.hash)
271 SetCachedTextureValid(job->m_url, job->m_details.updateable);
273 AddCachedTexture(job->m_url, job->m_details);
276 { // remove from our processing list
277 CSingleLock lock(m_processingSection);
278 std::set<CStdString>::iterator i = m_processinglist.find(job->m_url);
279 if (i != m_processinglist.end())
280 m_processinglist.erase(i);
283 m_completeEvent.Set();
285 // TODO: call back to the UI indicating that it can update it's image...
286 if (success && g_advancedSettings.m_useDDSFanart && !job->m_details.file.empty())
287 AddJob(new CTextureDDSJob(GetCachedPath(job->m_details.file)));
290 void CTextureCache::OnJobComplete(unsigned int jobID, bool success, CJob *job)
292 if (strcmp(job->GetType(), kJobTypeCacheImage) == 0)
293 OnCachingComplete(success, (CTextureCacheJob *)job);
294 return CJobQueue::OnJobComplete(jobID, success, job);
297 void CTextureCache::OnJobProgress(unsigned int jobID, unsigned int progress, unsigned int total, const CJob *job)
299 if (strcmp(job->GetType(), kJobTypeCacheImage) == 0 && !progress)
300 { // check our processing list
302 CSingleLock lock(m_processingSection);
303 const CTextureCacheJob *cacheJob = (CTextureCacheJob *)job;
304 std::set<CStdString>::iterator i = m_processinglist.find(cacheJob->m_url);
305 if (i == m_processinglist.end())
307 m_processinglist.insert(cacheJob->m_url);
314 CJobQueue::OnJobProgress(jobID, progress, total, job);
317 bool CTextureCache::Export(const CStdString &image, const CStdString &destination, bool overwrite)
319 CTextureDetails details;
320 CStdString cachedImage(GetCachedImage(image, details));
321 if (!cachedImage.empty())
323 CStdString dest = destination + URIUtils::GetExtension(cachedImage);
324 if (overwrite || !CFile::Exists(dest))
326 if (CFile::Cache(cachedImage, dest))
328 CLog::Log(LOGERROR, "%s failed exporting '%s' to '%s'", __FUNCTION__, cachedImage.c_str(), dest.c_str());
334 bool CTextureCache::Export(const CStdString &image, const CStdString &destination)
336 CTextureDetails details;
337 CStdString cachedImage(GetCachedImage(image, details));
338 if (!cachedImage.empty())
340 if (CFile::Cache(cachedImage, destination))
342 CLog::Log(LOGERROR, "%s failed exporting '%s' to '%s'", __FUNCTION__, cachedImage.c_str(), destination.c_str());