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"
31 #include "utils/StringUtils.h"
33 using namespace XFILE;
35 CTextureCache &CTextureCache::Get()
37 static CTextureCache s_cache;
41 CTextureCache::CTextureCache() : CJobQueue(false, 1, CJob::PRIORITY_LOW_PAUSABLE)
45 CTextureCache::~CTextureCache()
49 void CTextureCache::Initialize()
51 CSingleLock lock(m_databaseSection);
52 if (!m_database.IsOpen())
56 void CTextureCache::Deinitialize()
59 CSingleLock lock(m_databaseSection);
63 bool CTextureCache::IsCachedImage(const CStdString &url) const
65 if (url != "-" && !CURL::IsFullPath(url))
67 if (URIUtils::IsInPath(url, "special://skin/") ||
68 URIUtils::IsInPath(url, "androidapp://") ||
69 URIUtils::IsInPath(url, CProfilesManager::Get().GetThumbnailsFolder()))
74 bool CTextureCache::HasCachedImage(const CStdString &url)
76 CTextureDetails details;
77 CStdString cachedImage(GetCachedImage(url, details));
78 return (!cachedImage.IsEmpty() && cachedImage != url);
81 CStdString CTextureCache::GetCachedImage(const CStdString &image, CTextureDetails &details, bool trackUsage)
83 CStdString url = CTextureUtils::UnwrapImageURL(image);
85 if (IsCachedImage(url))
88 // lookup the item in the database
89 if (GetCachedTexture(url, details))
92 IncrementUseCount(details);
93 return GetCachedPath(details.file);
98 bool CTextureCache::CanCacheImageURL(const CURL &url)
100 return (url.GetUserName().empty() || url.GetUserName() == "music");
103 CStdString CTextureCache::CheckCachedImage(const CStdString &url, bool returnDDS, bool &needsRecaching)
105 CTextureDetails details;
106 CStdString path(GetCachedImage(url, details, true));
107 needsRecaching = !details.hash.empty();
110 if (!needsRecaching && returnDDS && !URIUtils::IsInPath(url, "special://skin/")) // TODO: should skin images be .dds'd (currently they're not necessarily writeable)
111 { // check for dds version
112 CStdString ddsPath = URIUtils::ReplaceExtension(path, ".dds");
113 if (CFile::Exists(ddsPath))
115 if (g_advancedSettings.m_useDDSFanart)
116 AddJob(new CTextureDDSJob(path));
123 void CTextureCache::BackgroundCacheImage(const CStdString &url)
125 CTextureDetails details;
126 CStdString path(GetCachedImage(url, details));
127 if (!path.IsEmpty() && details.hash.empty())
128 return; // image is already cached and doesn't need to be checked further
131 AddJob(new CTextureCacheJob(CTextureUtils::UnwrapImageURL(url), details.hash));
134 bool CTextureCache::CacheImage(const CStdString &image, CTextureDetails &details)
136 CStdString path = GetCachedImage(image, details);
137 if (path.empty()) // not cached
138 path = CacheImage(image, NULL, &details);
140 return !path.empty();
143 CStdString CTextureCache::CacheImage(const CStdString &image, CBaseTexture **texture, CTextureDetails *details)
145 CStdString url = CTextureUtils::UnwrapImageURL(image);
146 CSingleLock lock(m_processingSection);
147 if (m_processing.find(url) == m_processing.end())
149 m_processing.insert(url);
151 // cache the texture directly
152 CTextureCacheJob job(url);
153 bool success = job.CacheTexture(texture);
154 OnCachingComplete(success, &job);
155 if (success && details)
156 *details = job.m_details;
157 return success ? GetCachedPath(job.m_details.file) : "";
161 // wait for currently processing job to end.
164 m_completeEvent.WaitMSec(1000);
166 CSingleLock lock(m_processingSection);
167 if (m_processing.find(url) == m_processing.end())
171 CTextureDetails tempDetails;
173 details = &tempDetails;
174 return GetCachedImage(url, *details, true);
177 void CTextureCache::ClearCachedImage(const CStdString &url, bool deleteSource /*= false */)
179 // TODO: This can be removed when the texture cache covers everything.
180 CStdString path = deleteSource ? url : "";
181 CStdString cachedFile;
182 if (ClearCachedTexture(url, cachedFile))
183 path = GetCachedPath(cachedFile);
184 if (CFile::Exists(path))
186 path = URIUtils::ReplaceExtension(path, ".dds");
187 if (CFile::Exists(path))
191 bool CTextureCache::ClearCachedImage(int id)
193 CStdString cachedFile;
194 if (ClearCachedTexture(id, cachedFile))
196 cachedFile = GetCachedPath(cachedFile);
197 if (CFile::Exists(cachedFile))
198 CFile::Delete(cachedFile);
199 cachedFile = URIUtils::ReplaceExtension(cachedFile, ".dds");
200 if (CFile::Exists(cachedFile))
201 CFile::Delete(cachedFile);
207 bool CTextureCache::GetCachedTexture(const CStdString &url, CTextureDetails &details)
209 CSingleLock lock(m_databaseSection);
210 return m_database.GetCachedTexture(url, details);
213 bool CTextureCache::AddCachedTexture(const CStdString &url, const CTextureDetails &details)
215 CSingleLock lock(m_databaseSection);
216 return m_database.AddCachedTexture(url, details);
219 void CTextureCache::IncrementUseCount(const CTextureDetails &details)
221 static const size_t count_before_update = 100;
222 CSingleLock lock(m_useCountSection);
223 m_useCounts.reserve(count_before_update);
224 m_useCounts.push_back(details);
225 if (m_useCounts.size() >= count_before_update)
227 AddJob(new CTextureUseCountJob(m_useCounts));
232 bool CTextureCache::SetCachedTextureValid(const CStdString &url, bool updateable)
234 CSingleLock lock(m_databaseSection);
235 return m_database.SetCachedTextureValid(url, updateable);
238 bool CTextureCache::ClearCachedTexture(const CStdString &url, CStdString &cachedURL)
240 CSingleLock lock(m_databaseSection);
241 return m_database.ClearCachedTexture(url, cachedURL);
244 bool CTextureCache::ClearCachedTexture(int id, CStdString &cachedURL)
246 CSingleLock lock(m_databaseSection);
247 return m_database.ClearCachedTexture(id, cachedURL);
250 CStdString CTextureCache::GetCacheFile(const CStdString &url)
253 crc.ComputeFromLowerCase(url);
255 hex.Format("%08x", (unsigned int)crc);
257 hash.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_processing.find(job->m_url);
279 if (i != m_processing.end())
280 m_processing.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_processing.find(cacheJob->m_url);
305 if (i == m_processing.end())
307 m_processing.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.IsEmpty())
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.IsEmpty())
340 if (CFile::Cache(cachedImage, destination))
342 CLog::Log(LOGERROR, "%s failed exporting '%s' to '%s'", __FUNCTION__, cachedImage.c_str(), destination.c_str());