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()
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 = 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 CStdString CTextureCache::GetWrappedImageURL(const CStdString &image, const CStdString &type, const CStdString &options)
100 if (StringUtils::StartsWith(image, "image://"))
101 return image; // already wrapped
104 url.SetProtocol("image");
105 url.SetUserName(type);
106 url.SetHostName(image);
107 if (!options.IsEmpty())
109 url.SetFileName("transform");
110 url.SetOptions("?" + options);
115 CStdString CTextureCache::GetWrappedThumbURL(const CStdString &image)
117 return GetWrappedImageURL(image, "", "size=thumb");
120 CStdString CTextureCache::UnwrapImageURL(const CStdString &image)
122 if (StringUtils::StartsWith(image, "image://"))
125 if (url.GetUserName().IsEmpty() && url.GetOptions().IsEmpty())
126 return url.GetHostName();
131 bool CTextureCache::CanCacheImageURL(const CURL &url)
133 return (url.GetUserName().empty() || url.GetUserName() == "music");
136 CStdString CTextureCache::CheckCachedImage(const CStdString &url, bool returnDDS, bool &needsRecaching)
138 CTextureDetails details;
139 CStdString path(GetCachedImage(url, details, true));
140 needsRecaching = !details.hash.empty();
143 if (!needsRecaching && returnDDS && !URIUtils::IsInPath(url, "special://skin/")) // TODO: should skin images be .dds'd (currently they're not necessarily writeable)
144 { // check for dds version
145 CStdString ddsPath = URIUtils::ReplaceExtension(path, ".dds");
146 if (CFile::Exists(ddsPath))
148 if (g_advancedSettings.m_useDDSFanart)
149 AddJob(new CTextureDDSJob(path));
156 void CTextureCache::BackgroundCacheImage(const CStdString &url)
158 CTextureDetails details;
159 CStdString path(GetCachedImage(url, details));
160 if (!path.IsEmpty() && details.hash.empty())
161 return; // image is already cached and doesn't need to be checked further
164 AddJob(new CTextureCacheJob(UnwrapImageURL(url), details.hash));
167 bool CTextureCache::CacheImage(const CStdString &image, CTextureDetails &details)
169 CStdString path = GetCachedImage(image, details);
170 if (path.empty()) // not cached
171 path = CacheImage(image, NULL, &details);
173 return !path.empty();
176 CStdString CTextureCache::CacheImage(const CStdString &image, CBaseTexture **texture, CTextureDetails *details)
178 CStdString url = UnwrapImageURL(image);
179 CSingleLock lock(m_processingSection);
180 if (m_processing.find(url) == m_processing.end())
182 m_processing.insert(url);
184 // cache the texture directly
185 CTextureCacheJob job(url);
186 bool success = job.CacheTexture(texture);
187 OnCachingComplete(success, &job);
188 if (success && details)
189 *details = job.m_details;
190 return success ? GetCachedPath(job.m_details.file) : "";
194 // wait for currently processing job to end.
197 m_completeEvent.WaitMSec(1000);
199 CSingleLock lock(m_processingSection);
200 if (m_processing.find(url) == m_processing.end())
204 CTextureDetails tempDetails;
206 details = &tempDetails;
207 return GetCachedImage(url, *details, true);
210 void CTextureCache::ClearCachedImage(const CStdString &url, bool deleteSource /*= false */)
212 // TODO: This can be removed when the texture cache covers everything.
213 CStdString path = deleteSource ? url : "";
214 CStdString cachedFile;
215 if (ClearCachedTexture(url, cachedFile))
216 path = GetCachedPath(cachedFile);
217 if (CFile::Exists(path))
219 path = URIUtils::ReplaceExtension(path, ".dds");
220 if (CFile::Exists(path))
224 bool CTextureCache::GetCachedTexture(const CStdString &url, CTextureDetails &details)
226 CSingleLock lock(m_databaseSection);
227 return m_database.GetCachedTexture(url, details);
230 bool CTextureCache::AddCachedTexture(const CStdString &url, const CTextureDetails &details)
232 CSingleLock lock(m_databaseSection);
233 return m_database.AddCachedTexture(url, details);
236 void CTextureCache::IncrementUseCount(const CTextureDetails &details)
238 static const size_t count_before_update = 100;
239 CSingleLock lock(m_useCountSection);
240 m_useCounts.reserve(count_before_update);
241 m_useCounts.push_back(details);
242 if (m_useCounts.size() >= count_before_update)
244 AddJob(new CTextureUseCountJob(m_useCounts));
249 bool CTextureCache::SetCachedTextureValid(const CStdString &url, bool updateable)
251 CSingleLock lock(m_databaseSection);
252 return m_database.SetCachedTextureValid(url, updateable);
255 bool CTextureCache::ClearCachedTexture(const CStdString &url, CStdString &cachedURL)
257 CSingleLock lock(m_databaseSection);
258 return m_database.ClearCachedTexture(url, cachedURL);
261 CStdString CTextureCache::GetCacheFile(const CStdString &url)
264 crc.ComputeFromLowerCase(url);
266 hex.Format("%08x", (unsigned int)crc);
268 hash.Format("%c/%s", hex[0], hex.c_str());
272 CStdString CTextureCache::GetCachedPath(const CStdString &file)
274 return URIUtils::AddFileToFolder(CProfilesManager::Get().GetThumbnailsFolder(), file);
277 void CTextureCache::OnCachingComplete(bool success, CTextureCacheJob *job)
281 if (job->m_oldHash == job->m_details.hash)
282 SetCachedTextureValid(job->m_url, job->m_details.updateable);
284 AddCachedTexture(job->m_url, job->m_details);
287 { // remove from our processing list
288 CSingleLock lock(m_processingSection);
289 std::set<CStdString>::iterator i = m_processing.find(job->m_url);
290 if (i != m_processing.end())
291 m_processing.erase(i);
294 m_completeEvent.Set();
296 // TODO: call back to the UI indicating that it can update it's image...
297 if (success && g_advancedSettings.m_useDDSFanart && !job->m_details.file.empty())
298 AddJob(new CTextureDDSJob(GetCachedPath(job->m_details.file)));
301 void CTextureCache::OnJobComplete(unsigned int jobID, bool success, CJob *job)
303 if (strcmp(job->GetType(), kJobTypeCacheImage) == 0)
304 OnCachingComplete(success, (CTextureCacheJob *)job);
305 return CJobQueue::OnJobComplete(jobID, success, job);
308 void CTextureCache::OnJobProgress(unsigned int jobID, unsigned int progress, unsigned int total, const CJob *job)
310 if (strcmp(job->GetType(), kJobTypeCacheImage) == 0 && !progress)
311 { // check our processing list
313 CSingleLock lock(m_processingSection);
314 const CTextureCacheJob *cacheJob = (CTextureCacheJob *)job;
315 std::set<CStdString>::iterator i = m_processing.find(cacheJob->m_url);
316 if (i == m_processing.end())
318 m_processing.insert(cacheJob->m_url);
325 CJobQueue::OnJobProgress(jobID, progress, total, job);
328 bool CTextureCache::Export(const CStdString &image, const CStdString &destination, bool overwrite)
330 CTextureDetails details;
331 CStdString cachedImage(GetCachedImage(image, details));
332 if (!cachedImage.IsEmpty())
334 CStdString dest = destination + URIUtils::GetExtension(cachedImage);
335 if (overwrite || !CFile::Exists(dest))
337 if (CFile::Cache(cachedImage, dest))
339 CLog::Log(LOGERROR, "%s failed exporting '%s' to '%s'", __FUNCTION__, cachedImage.c_str(), dest.c_str());
345 bool CTextureCache::Export(const CStdString &image, const CStdString &destination)
347 CTextureDetails details;
348 CStdString cachedImage(GetCachedImage(image, details));
349 if (!cachedImage.IsEmpty())
351 if (CFile::Cache(cachedImage, destination))
353 CLog::Log(LOGERROR, "%s failed exporting '%s' to '%s'", __FUNCTION__, cachedImage.c_str(), destination.c_str());