[textures] adds ClearCachedImage() to clear a texture by id
[vuplus_xbmc] / xbmc / TextureCache.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
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)
8  *  any later version.
9  *
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.
14  *
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/>.
18  *
19  */
20
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 "URL.h"
31 #include "utils/StringUtils.h"
32
33 using namespace XFILE;
34
35 CTextureCache &CTextureCache::Get()
36 {
37   static CTextureCache s_cache;
38   return s_cache;
39 }
40
41 CTextureCache::CTextureCache() : CJobQueue(false, 1, CJob::PRIORITY_LOW_PAUSABLE)
42 {
43 }
44
45 CTextureCache::~CTextureCache()
46 {
47 }
48
49 void CTextureCache::Initialize()
50 {
51   CSingleLock lock(m_databaseSection);
52   if (!m_database.IsOpen())
53     m_database.Open();
54 }
55
56 void CTextureCache::Deinitialize()
57 {
58   CancelJobs();
59   CSingleLock lock(m_databaseSection);
60   m_database.Close();
61 }
62
63 bool CTextureCache::IsCachedImage(const CStdString &url) const
64 {
65   if (url != "-" && !CURL::IsFullPath(url))
66     return true;
67   if (URIUtils::IsInPath(url, "special://skin/") ||
68       URIUtils::IsInPath(url, "androidapp://")   ||
69       URIUtils::IsInPath(url, CProfilesManager::Get().GetThumbnailsFolder()))
70     return true;
71   return false;
72 }
73
74 bool CTextureCache::HasCachedImage(const CStdString &url)
75 {
76   CTextureDetails details;
77   CStdString cachedImage(GetCachedImage(url, details));
78   return (!cachedImage.IsEmpty() && cachedImage != url);
79 }
80
81 CStdString CTextureCache::GetCachedImage(const CStdString &image, CTextureDetails &details, bool trackUsage)
82 {
83   CStdString url = UnwrapImageURL(image);
84
85   if (IsCachedImage(url))
86     return url;
87
88   // lookup the item in the database
89   if (GetCachedTexture(url, details))
90   {
91     if (trackUsage)
92       IncrementUseCount(details);
93     return GetCachedPath(details.file);
94   }
95   return "";
96 }
97
98 CStdString CTextureCache::GetWrappedImageURL(const CStdString &image, const CStdString &type, const CStdString &options)
99 {
100   if (StringUtils::StartsWith(image, "image://"))
101     return image; // already wrapped
102
103   CURL url;
104   url.SetProtocol("image");
105   url.SetUserName(type);
106   url.SetHostName(image);
107   if (!options.IsEmpty())
108   {
109     url.SetFileName("transform");
110     url.SetOptions("?" + options);
111   }
112   return url.Get();
113 }
114
115 CStdString CTextureCache::GetWrappedThumbURL(const CStdString &image)
116 {
117   return GetWrappedImageURL(image, "", "size=thumb");
118 }
119
120 CStdString CTextureCache::UnwrapImageURL(const CStdString &image)
121 {
122   if (StringUtils::StartsWith(image, "image://"))
123   {
124     CURL url(image);
125     if (url.GetUserName().IsEmpty() && url.GetOptions().IsEmpty())
126       return url.GetHostName();
127   }
128   return image;
129 }
130
131 bool CTextureCache::CanCacheImageURL(const CURL &url)
132 {
133   return (url.GetUserName().empty() || url.GetUserName() == "music");
134 }
135
136 CStdString CTextureCache::CheckCachedImage(const CStdString &url, bool returnDDS, bool &needsRecaching)
137 {
138   CTextureDetails details;
139   CStdString path(GetCachedImage(url, details, true));
140   needsRecaching = !details.hash.empty();
141   if (!path.IsEmpty())
142   {
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))
147         return ddsPath;
148       if (g_advancedSettings.m_useDDSFanart)
149         AddJob(new CTextureDDSJob(path));
150     }
151     return path;
152   }
153   return "";
154 }
155
156 void CTextureCache::BackgroundCacheImage(const CStdString &url)
157 {
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
162
163   // needs (re)caching
164   AddJob(new CTextureCacheJob(UnwrapImageURL(url), details.hash));
165 }
166
167 bool CTextureCache::CacheImage(const CStdString &image, CTextureDetails &details)
168 {
169   CStdString path = GetCachedImage(image, details);
170   if (path.empty()) // not cached
171     path = CacheImage(image, NULL, &details);
172
173   return !path.empty();
174 }
175
176 CStdString CTextureCache::CacheImage(const CStdString &image, CBaseTexture **texture, CTextureDetails *details)
177 {
178   CStdString url = UnwrapImageURL(image);
179   CSingleLock lock(m_processingSection);
180   if (m_processing.find(url) == m_processing.end())
181   {
182     m_processing.insert(url);
183     lock.Leave();
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) : "";
191   }
192   lock.Leave();
193
194   // wait for currently processing job to end.
195   while (true)
196   {
197     m_completeEvent.WaitMSec(1000);
198     {
199       CSingleLock lock(m_processingSection);
200       if (m_processing.find(url) == m_processing.end())
201         break;
202     }
203   }
204   CTextureDetails tempDetails;
205   if (!details)
206     details = &tempDetails;
207   return GetCachedImage(url, *details, true);
208 }
209
210 void CTextureCache::ClearCachedImage(const CStdString &url, bool deleteSource /*= false */)
211 {
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))
218     CFile::Delete(path);
219   path = URIUtils::ReplaceExtension(path, ".dds");
220   if (CFile::Exists(path))
221     CFile::Delete(path);
222 }
223
224 bool CTextureCache::ClearCachedImage(int id)
225 {
226   CStdString cachedFile;
227   if (ClearCachedTexture(id, cachedFile))
228   {
229     cachedFile = GetCachedPath(cachedFile);
230     if (CFile::Exists(cachedFile))
231       CFile::Delete(cachedFile);
232     cachedFile = URIUtils::ReplaceExtension(cachedFile, ".dds");
233     if (CFile::Exists(cachedFile))
234       CFile::Delete(cachedFile);
235     return true;
236   }
237   return false;
238 }
239
240 bool CTextureCache::GetCachedTexture(const CStdString &url, CTextureDetails &details)
241 {
242   CSingleLock lock(m_databaseSection);
243   return m_database.GetCachedTexture(url, details);
244 }
245
246 bool CTextureCache::AddCachedTexture(const CStdString &url, const CTextureDetails &details)
247 {
248   CSingleLock lock(m_databaseSection);
249   return m_database.AddCachedTexture(url, details);
250 }
251
252 void CTextureCache::IncrementUseCount(const CTextureDetails &details)
253 {
254   static const size_t count_before_update = 100;
255   CSingleLock lock(m_useCountSection);
256   m_useCounts.reserve(count_before_update);
257   m_useCounts.push_back(details);
258   if (m_useCounts.size() >= count_before_update)
259   {
260     AddJob(new CTextureUseCountJob(m_useCounts));
261     m_useCounts.clear();
262   }
263 }
264
265 bool CTextureCache::SetCachedTextureValid(const CStdString &url, bool updateable)
266 {
267   CSingleLock lock(m_databaseSection);
268   return m_database.SetCachedTextureValid(url, updateable);
269 }
270
271 bool CTextureCache::ClearCachedTexture(const CStdString &url, CStdString &cachedURL)
272 {
273   CSingleLock lock(m_databaseSection);
274   return m_database.ClearCachedTexture(url, cachedURL);
275 }
276
277 bool CTextureCache::ClearCachedTexture(int id, CStdString &cachedURL)
278 {
279   CSingleLock lock(m_databaseSection);
280   return m_database.ClearCachedTexture(id, cachedURL);
281 }
282
283 CStdString CTextureCache::GetCacheFile(const CStdString &url)
284 {
285   Crc32 crc;
286   crc.ComputeFromLowerCase(url);
287   CStdString hex;
288   hex.Format("%08x", (unsigned int)crc);
289   CStdString hash;
290   hash.Format("%c/%s", hex[0], hex.c_str());
291   return hash;
292 }
293
294 CStdString CTextureCache::GetCachedPath(const CStdString &file)
295 {
296   return URIUtils::AddFileToFolder(CProfilesManager::Get().GetThumbnailsFolder(), file);
297 }
298
299 void CTextureCache::OnCachingComplete(bool success, CTextureCacheJob *job)
300 {
301   if (success)
302   {
303     if (job->m_oldHash == job->m_details.hash)
304       SetCachedTextureValid(job->m_url, job->m_details.updateable);
305     else
306       AddCachedTexture(job->m_url, job->m_details);
307   }
308
309   { // remove from our processing list
310     CSingleLock lock(m_processingSection);
311     std::set<CStdString>::iterator i = m_processing.find(job->m_url);
312     if (i != m_processing.end())
313       m_processing.erase(i);
314   }
315
316   m_completeEvent.Set();
317
318   // TODO: call back to the UI indicating that it can update it's image...
319   if (success && g_advancedSettings.m_useDDSFanart && !job->m_details.file.empty())
320     AddJob(new CTextureDDSJob(GetCachedPath(job->m_details.file)));
321 }
322
323 void CTextureCache::OnJobComplete(unsigned int jobID, bool success, CJob *job)
324 {
325   if (strcmp(job->GetType(), kJobTypeCacheImage) == 0)
326     OnCachingComplete(success, (CTextureCacheJob *)job);
327   return CJobQueue::OnJobComplete(jobID, success, job);
328 }
329
330 void CTextureCache::OnJobProgress(unsigned int jobID, unsigned int progress, unsigned int total, const CJob *job)
331 {
332   if (strcmp(job->GetType(), kJobTypeCacheImage) == 0 && !progress)
333   { // check our processing list
334     {
335       CSingleLock lock(m_processingSection);
336       const CTextureCacheJob *cacheJob = (CTextureCacheJob *)job;
337       std::set<CStdString>::iterator i = m_processing.find(cacheJob->m_url);
338       if (i == m_processing.end())
339       {
340         m_processing.insert(cacheJob->m_url);
341         return;
342       }
343     }
344     CancelJob(job);
345   }
346   else
347     CJobQueue::OnJobProgress(jobID, progress, total, job);
348 }
349
350 bool CTextureCache::Export(const CStdString &image, const CStdString &destination, bool overwrite)
351 {
352   CTextureDetails details;
353   CStdString cachedImage(GetCachedImage(image, details));
354   if (!cachedImage.IsEmpty())
355   {
356     CStdString dest = destination + URIUtils::GetExtension(cachedImage);
357     if (overwrite || !CFile::Exists(dest))
358     {
359       if (CFile::Cache(cachedImage, dest))
360         return true;
361       CLog::Log(LOGERROR, "%s failed exporting '%s' to '%s'", __FUNCTION__, cachedImage.c_str(), dest.c_str());
362     }
363   }
364   return false;
365 }
366
367 bool CTextureCache::Export(const CStdString &image, const CStdString &destination)
368 {
369   CTextureDetails details;
370   CStdString cachedImage(GetCachedImage(image, details));
371   if (!cachedImage.IsEmpty())
372   {
373     if (CFile::Cache(cachedImage, destination))
374       return true;
375     CLog::Log(LOGERROR, "%s failed exporting '%s' to '%s'", __FUNCTION__, cachedImage.c_str(), destination.c_str());
376   }
377   return false;
378 }