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