Remove LiveTV menu.
[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 "utils/StringUtils.h"
31 #include "URL.h"
32 #include "utils/StringUtils.h"
33
34 using namespace XFILE;
35
36 CTextureCache &CTextureCache::Get()
37 {
38   static CTextureCache s_cache;
39   return s_cache;
40 }
41
42 CTextureCache::CTextureCache() : CJobQueue(false, 1, CJob::PRIORITY_LOW_PAUSABLE)
43 {
44 }
45
46 CTextureCache::~CTextureCache()
47 {
48 }
49
50 void CTextureCache::Initialize()
51 {
52   CSingleLock lock(m_databaseSection);
53   if (!m_database.IsOpen())
54     m_database.Open();
55 }
56
57 void CTextureCache::Deinitialize()
58 {
59   CancelJobs();
60   CSingleLock lock(m_databaseSection);
61   m_database.Close();
62 }
63
64 bool CTextureCache::IsCachedImage(const CStdString &url) const
65 {
66   if (url != "-" && !CURL::IsFullPath(url))
67     return true;
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()))
72     return true;
73   return false;
74 }
75
76 bool CTextureCache::HasCachedImage(const CStdString &url)
77 {
78   CTextureDetails details;
79   CStdString cachedImage(GetCachedImage(url, details));
80   return (!cachedImage.empty() && cachedImage != url);
81 }
82
83 CStdString CTextureCache::GetCachedImage(const CStdString &image, CTextureDetails &details, bool trackUsage)
84 {
85   CStdString url = CTextureUtils::UnwrapImageURL(image);
86
87   if (IsCachedImage(url))
88     return url;
89
90   // lookup the item in the database
91   if (GetCachedTexture(url, details))
92   {
93     if (trackUsage)
94       IncrementUseCount(details);
95     return GetCachedPath(details.file);
96   }
97   return "";
98 }
99
100 bool CTextureCache::CanCacheImageURL(const CURL &url)
101 {
102   return (url.GetUserName().empty() || url.GetUserName() == "music");
103 }
104
105 CStdString CTextureCache::CheckCachedImage(const CStdString &url, bool returnDDS, bool &needsRecaching)
106 {
107   CTextureDetails details;
108   CStdString path(GetCachedImage(url, details, true));
109   needsRecaching = !details.hash.empty();
110   if (!path.empty())
111   {
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))
116         return ddsPath;
117       if (g_advancedSettings.m_useDDSFanart)
118         AddJob(new CTextureDDSJob(path));
119     }
120     return path;
121   }
122   return "";
123 }
124
125 void CTextureCache::BackgroundCacheImage(const CStdString &url)
126 {
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
131
132   // needs (re)caching
133   AddJob(new CTextureCacheJob(CTextureUtils::UnwrapImageURL(url), details.hash));
134 }
135
136 bool CTextureCache::CacheImage(const CStdString &image, CTextureDetails &details)
137 {
138   CStdString path = GetCachedImage(image, details);
139   if (path.empty()) // not cached
140     path = CacheImage(image, NULL, &details);
141
142   return !path.empty();
143 }
144
145 CStdString CTextureCache::CacheImage(const CStdString &image, CBaseTexture **texture, CTextureDetails *details)
146 {
147   CStdString url = CTextureUtils::UnwrapImageURL(image);
148   CSingleLock lock(m_processingSection);
149   if (m_processinglist.find(url) == m_processinglist.end())
150   {
151     m_processinglist.insert(url);
152     lock.Leave();
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) : "";
160   }
161   lock.Leave();
162
163   // wait for currently processing job to end.
164   while (true)
165   {
166     m_completeEvent.WaitMSec(1000);
167     {
168       CSingleLock lock(m_processingSection);
169       if (m_processinglist.find(url) == m_processinglist.end())
170         break;
171     }
172   }
173   CTextureDetails tempDetails;
174   if (!details)
175     details = &tempDetails;
176   return GetCachedImage(url, *details, true);
177 }
178
179 void CTextureCache::ClearCachedImage(const CStdString &url, bool deleteSource /*= false */)
180 {
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))
187     CFile::Delete(path);
188   path = URIUtils::ReplaceExtension(path, ".dds");
189   if (CFile::Exists(path))
190     CFile::Delete(path);
191 }
192
193 bool CTextureCache::ClearCachedImage(int id)
194 {
195   CStdString cachedFile;
196   if (ClearCachedTexture(id, cachedFile))
197   {
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);
204     return true;
205   }
206   return false;
207 }
208
209 bool CTextureCache::GetCachedTexture(const CStdString &url, CTextureDetails &details)
210 {
211   CSingleLock lock(m_databaseSection);
212   return m_database.GetCachedTexture(url, details);
213 }
214
215 bool CTextureCache::AddCachedTexture(const CStdString &url, const CTextureDetails &details)
216 {
217   CSingleLock lock(m_databaseSection);
218   return m_database.AddCachedTexture(url, details);
219 }
220
221 void CTextureCache::IncrementUseCount(const CTextureDetails &details)
222 {
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)
228   {
229     AddJob(new CTextureUseCountJob(m_useCounts));
230     m_useCounts.clear();
231   }
232 }
233
234 bool CTextureCache::SetCachedTextureValid(const CStdString &url, bool updateable)
235 {
236   CSingleLock lock(m_databaseSection);
237   return m_database.SetCachedTextureValid(url, updateable);
238 }
239
240 bool CTextureCache::ClearCachedTexture(const CStdString &url, CStdString &cachedURL)
241 {
242   CSingleLock lock(m_databaseSection);
243   return m_database.ClearCachedTexture(url, cachedURL);
244 }
245
246 bool CTextureCache::ClearCachedTexture(int id, CStdString &cachedURL)
247 {
248   CSingleLock lock(m_databaseSection);
249   return m_database.ClearCachedTexture(id, cachedURL);
250 }
251
252 CStdString CTextureCache::GetCacheFile(const CStdString &url)
253 {
254   Crc32 crc;
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());
258   return hash;
259 }
260
261 CStdString CTextureCache::GetCachedPath(const CStdString &file)
262 {
263   return URIUtils::AddFileToFolder(CProfilesManager::Get().GetThumbnailsFolder(), file);
264 }
265
266 void CTextureCache::OnCachingComplete(bool success, CTextureCacheJob *job)
267 {
268   if (success)
269   {
270     if (job->m_oldHash == job->m_details.hash)
271       SetCachedTextureValid(job->m_url, job->m_details.updateable);
272     else
273       AddCachedTexture(job->m_url, job->m_details);
274   }
275
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);
281   }
282
283   m_completeEvent.Set();
284
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)));
288 }
289
290 void CTextureCache::OnJobComplete(unsigned int jobID, bool success, CJob *job)
291 {
292   if (strcmp(job->GetType(), kJobTypeCacheImage) == 0)
293     OnCachingComplete(success, (CTextureCacheJob *)job);
294   return CJobQueue::OnJobComplete(jobID, success, job);
295 }
296
297 void CTextureCache::OnJobProgress(unsigned int jobID, unsigned int progress, unsigned int total, const CJob *job)
298 {
299   if (strcmp(job->GetType(), kJobTypeCacheImage) == 0 && !progress)
300   { // check our processing list
301     {
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())
306       {
307         m_processinglist.insert(cacheJob->m_url);
308         return;
309       }
310     }
311     CancelJob(job);
312   }
313   else
314     CJobQueue::OnJobProgress(jobID, progress, total, job);
315 }
316
317 bool CTextureCache::Export(const CStdString &image, const CStdString &destination, bool overwrite)
318 {
319   CTextureDetails details;
320   CStdString cachedImage(GetCachedImage(image, details));
321   if (!cachedImage.empty())
322   {
323     CStdString dest = destination + URIUtils::GetExtension(cachedImage);
324     if (overwrite || !CFile::Exists(dest))
325     {
326       if (CFile::Cache(cachedImage, dest))
327         return true;
328       CLog::Log(LOGERROR, "%s failed exporting '%s' to '%s'", __FUNCTION__, cachedImage.c_str(), dest.c_str());
329     }
330   }
331   return false;
332 }
333
334 bool CTextureCache::Export(const CStdString &image, const CStdString &destination)
335 {
336   CTextureDetails details;
337   CStdString cachedImage(GetCachedImage(image, details));
338   if (!cachedImage.empty())
339   {
340     if (CFile::Cache(cachedImage, destination))
341       return true;
342     CLog::Log(LOGERROR, "%s failed exporting '%s' to '%s'", __FUNCTION__, cachedImage.c_str(), destination.c_str());
343   }
344   return false;
345 }