2 * Copyright (C) 2005-2012 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 "threads/SingleLock.h"
25 #include "utils/Crc32.h"
26 #include "settings/Settings.h"
27 #include "settings/AdvancedSettings.h"
28 #include "utils/log.h"
29 #include "utils/URIUtils.h"
32 using namespace XFILE;
34 CTextureCache &CTextureCache::Get()
36 static CTextureCache s_cache;
40 CTextureCache::CTextureCache()
44 CTextureCache::~CTextureCache()
48 void CTextureCache::Initialize()
50 CSingleLock lock(m_databaseSection);
51 if (!m_database.IsOpen())
55 void CTextureCache::Deinitialize()
58 CSingleLock lock(m_databaseSection);
62 bool CTextureCache::IsCachedImage(const CStdString &url) const
64 if (url != "-" && !CURL::IsFullPath(url))
66 if (URIUtils::IsInPath(url, "special://skin/") ||
67 URIUtils::IsInPath(url, "androidapp://") ||
68 URIUtils::IsInPath(url, g_settings.GetThumbnailsFolder()))
73 bool CTextureCache::HasCachedImage(const CStdString &url)
75 CTextureDetails details;
76 CStdString cachedImage(GetCachedImage(url, details));
77 return (!cachedImage.IsEmpty() && cachedImage != url);
80 CStdString CTextureCache::GetCachedImage(const CStdString &image, CTextureDetails &details, bool trackUsage)
82 CStdString url = UnwrapImageURL(image);
84 if (IsCachedImage(url))
87 // lookup the item in the database
88 if (GetCachedTexture(url, details))
91 IncrementUseCount(details);
92 return GetCachedPath(details.file);
97 CStdString CTextureCache::GetWrappedImageURL(const CStdString &image, const CStdString &type, const CStdString &options)
99 if (image.compare(0, 8, "image://") == 0)
100 return image; // already wrapped
103 url.SetProtocol("image");
104 url.SetUserName(type);
105 url.SetHostName(image);
106 if (!options.IsEmpty())
108 url.SetFileName("transform");
109 url.SetOptions("?" + options);
114 CStdString CTextureCache::GetWrappedThumbURL(const CStdString &image)
116 return GetWrappedImageURL(image, "", "size=thumb");
119 CStdString CTextureCache::UnwrapImageURL(const CStdString &image)
121 if (image.compare(0, 8, "image://") == 0)
124 if (url.GetUserName().IsEmpty() && url.GetOptions().IsEmpty())
125 return url.GetHostName();
130 bool CTextureCache::CanCacheImageURL(const CURL &url)
132 return (url.GetUserName().empty() || url.GetUserName() == "music");
135 CStdString CTextureCache::CheckCachedImage(const CStdString &url, bool returnDDS, bool &needsRecaching)
137 CTextureDetails details;
138 CStdString path(GetCachedImage(url, details, true));
139 needsRecaching = !details.hash.empty();
142 if (!needsRecaching && returnDDS && !URIUtils::IsInPath(url, "special://skin/")) // TODO: should skin images be .dds'd (currently they're not necessarily writeable)
143 { // check for dds version
144 CStdString ddsPath = URIUtils::ReplaceExtension(path, ".dds");
145 if (CFile::Exists(ddsPath))
147 if (g_advancedSettings.m_useDDSFanart)
148 AddJob(new CTextureDDSJob(path));
155 void CTextureCache::BackgroundCacheImage(const CStdString &url)
157 CTextureDetails details;
158 CStdString path(GetCachedImage(url, details));
159 if (!path.IsEmpty() && details.hash.empty())
160 return; // image is already cached and doesn't need to be checked further
163 AddJob(new CTextureCacheJob(UnwrapImageURL(url), details.hash));
166 bool CTextureCache::CacheImage(const CStdString &image, CTextureDetails &details)
168 CStdString path = GetCachedImage(image, details);
169 if (path.empty()) // not cached
170 path = CacheImage(image, NULL, &details);
172 return !path.empty();
175 CStdString CTextureCache::CacheImage(const CStdString &image, CBaseTexture **texture, CTextureDetails *details)
177 CStdString url = UnwrapImageURL(image);
178 CSingleLock lock(m_processingSection);
179 if (m_processing.find(url) == m_processing.end())
181 m_processing.insert(url);
183 // cache the texture directly
184 CTextureCacheJob job(url);
185 bool success = job.CacheTexture(texture);
186 OnCachingComplete(success, &job);
187 if (success && details)
188 *details = job.m_details;
189 return success ? GetCachedPath(job.m_details.file) : "";
193 // wait for currently processing job to end.
196 m_completeEvent.WaitMSec(1000);
198 CSingleLock lock(m_processingSection);
199 if (m_processing.find(url) == m_processing.end())
203 CTextureDetails tempDetails;
205 details = &tempDetails;
206 return GetCachedImage(url, *details, true);
209 void CTextureCache::ClearCachedImage(const CStdString &url, bool deleteSource /*= false */)
211 // TODO: This can be removed when the texture cache covers everything.
212 CStdString path = deleteSource ? url : "";
213 CStdString cachedFile;
214 if (ClearCachedTexture(url, cachedFile))
215 path = GetCachedPath(cachedFile);
216 if (CFile::Exists(path))
218 path = URIUtils::ReplaceExtension(path, ".dds");
219 if (CFile::Exists(path))
223 bool CTextureCache::GetCachedTexture(const CStdString &url, CTextureDetails &details)
225 CSingleLock lock(m_databaseSection);
226 return m_database.GetCachedTexture(url, details);
229 bool CTextureCache::AddCachedTexture(const CStdString &url, const CTextureDetails &details)
231 CSingleLock lock(m_databaseSection);
232 return m_database.AddCachedTexture(url, details);
235 void CTextureCache::IncrementUseCount(const CTextureDetails &details)
237 static const size_t count_before_update = 100;
238 CSingleLock lock(m_useCountSection);
239 m_useCounts.reserve(count_before_update);
240 m_useCounts.push_back(details);
241 if (m_useCounts.size() >= count_before_update)
243 AddJob(new CTextureUseCountJob(m_useCounts));
248 bool CTextureCache::SetCachedTextureValid(const CStdString &url, bool updateable)
250 CSingleLock lock(m_databaseSection);
251 return m_database.SetCachedTextureValid(url, updateable);
254 bool CTextureCache::ClearCachedTexture(const CStdString &url, CStdString &cachedURL)
256 CSingleLock lock(m_databaseSection);
257 return m_database.ClearCachedTexture(url, cachedURL);
260 CStdString CTextureCache::GetCacheFile(const CStdString &url)
263 crc.ComputeFromLowerCase(url);
265 hex.Format("%08x", (unsigned int)crc);
267 hash.Format("%c/%s", hex[0], hex.c_str());
271 CStdString CTextureCache::GetCachedPath(const CStdString &file)
273 return URIUtils::AddFileToFolder(g_settings.GetThumbnailsFolder(), file);
276 void CTextureCache::OnCachingComplete(bool success, CTextureCacheJob *job)
280 if (job->m_oldHash == job->m_details.hash)
281 SetCachedTextureValid(job->m_url, job->m_details.updateable);
283 AddCachedTexture(job->m_url, job->m_details);
286 { // remove from our processing list
287 CSingleLock lock(m_processingSection);
288 std::set<CStdString>::iterator i = m_processing.find(job->m_url);
289 if (i != m_processing.end())
290 m_processing.erase(i);
293 m_completeEvent.Set();
295 // TODO: call back to the UI indicating that it can update it's image...
296 if (success && g_advancedSettings.m_useDDSFanart && !job->m_details.file.empty())
297 AddJob(new CTextureDDSJob(GetCachedPath(job->m_details.file)));
300 void CTextureCache::OnJobComplete(unsigned int jobID, bool success, CJob *job)
302 if (strcmp(job->GetType(), "cacheimage") == 0)
303 OnCachingComplete(success, (CTextureCacheJob *)job);
304 return CJobQueue::OnJobComplete(jobID, success, job);
307 void CTextureCache::OnJobProgress(unsigned int jobID, unsigned int progress, unsigned int total, const CJob *job)
309 if (strcmp(job->GetType(), "cacheimage") == 0 && !progress)
310 { // check our processing list
312 CSingleLock lock(m_processingSection);
313 const CTextureCacheJob *cacheJob = (CTextureCacheJob *)job;
314 std::set<CStdString>::iterator i = m_processing.find(cacheJob->m_url);
315 if (i == m_processing.end())
317 m_processing.insert(cacheJob->m_url);
324 CJobQueue::OnJobProgress(jobID, progress, total, job);
327 bool CTextureCache::Export(const CStdString &image, const CStdString &destination, bool overwrite)
329 CTextureDetails details;
330 CStdString cachedImage(GetCachedImage(image, details));
331 if (!cachedImage.IsEmpty())
333 CStdString dest = destination + URIUtils::GetExtension(cachedImage);
334 if (overwrite || !CFile::Exists(dest))
336 if (CFile::Cache(cachedImage, dest))
338 CLog::Log(LOGERROR, "%s failed exporting '%s' to '%s'", __FUNCTION__, cachedImage.c_str(), dest.c_str());
344 bool CTextureCache::Export(const CStdString &image, const CStdString &destination)
346 CTextureDetails details;
347 CStdString cachedImage(GetCachedImage(image, details));
348 if (!cachedImage.IsEmpty())
350 if (CFile::Cache(cachedImage, destination))
352 CLog::Log(LOGERROR, "%s failed exporting '%s' to '%s'", __FUNCTION__, cachedImage.c_str(), destination.c_str());