Merge pull request #3534 from Karlson2k/cfile_fix_01
[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 = CTextureUtils::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 bool CTextureCache::CanCacheImageURL(const CURL &url)
99 {
100   return (url.GetUserName().empty() || url.GetUserName() == "music");
101 }
102
103 CStdString CTextureCache::CheckCachedImage(const CStdString &url, bool returnDDS, bool &needsRecaching)
104 {
105   CTextureDetails details;
106   CStdString path(GetCachedImage(url, details, true));
107   needsRecaching = !details.hash.empty();
108   if (!path.IsEmpty())
109   {
110     if (!needsRecaching && returnDDS && !URIUtils::IsInPath(url, "special://skin/")) // TODO: should skin images be .dds'd (currently they're not necessarily writeable)
111     { // check for dds version
112       CStdString ddsPath = URIUtils::ReplaceExtension(path, ".dds");
113       if (CFile::Exists(ddsPath))
114         return ddsPath;
115       if (g_advancedSettings.m_useDDSFanart)
116         AddJob(new CTextureDDSJob(path));
117     }
118     return path;
119   }
120   return "";
121 }
122
123 void CTextureCache::BackgroundCacheImage(const CStdString &url)
124 {
125   CTextureDetails details;
126   CStdString path(GetCachedImage(url, details));
127   if (!path.IsEmpty() && details.hash.empty())
128     return; // image is already cached and doesn't need to be checked further
129
130   // needs (re)caching
131   AddJob(new CTextureCacheJob(CTextureUtils::UnwrapImageURL(url), details.hash));
132 }
133
134 bool CTextureCache::CacheImage(const CStdString &image, CTextureDetails &details)
135 {
136   CStdString path = GetCachedImage(image, details);
137   if (path.empty()) // not cached
138     path = CacheImage(image, NULL, &details);
139
140   return !path.empty();
141 }
142
143 CStdString CTextureCache::CacheImage(const CStdString &image, CBaseTexture **texture, CTextureDetails *details)
144 {
145   CStdString url = CTextureUtils::UnwrapImageURL(image);
146   CSingleLock lock(m_processingSection);
147   if (m_processing.find(url) == m_processing.end())
148   {
149     m_processing.insert(url);
150     lock.Leave();
151     // cache the texture directly
152     CTextureCacheJob job(url);
153     bool success = job.CacheTexture(texture);
154     OnCachingComplete(success, &job);
155     if (success && details)
156       *details = job.m_details;
157     return success ? GetCachedPath(job.m_details.file) : "";
158   }
159   lock.Leave();
160
161   // wait for currently processing job to end.
162   while (true)
163   {
164     m_completeEvent.WaitMSec(1000);
165     {
166       CSingleLock lock(m_processingSection);
167       if (m_processing.find(url) == m_processing.end())
168         break;
169     }
170   }
171   CTextureDetails tempDetails;
172   if (!details)
173     details = &tempDetails;
174   return GetCachedImage(url, *details, true);
175 }
176
177 void CTextureCache::ClearCachedImage(const CStdString &url, bool deleteSource /*= false */)
178 {
179   // TODO: This can be removed when the texture cache covers everything.
180   CStdString path = deleteSource ? url : "";
181   CStdString cachedFile;
182   if (ClearCachedTexture(url, cachedFile))
183     path = GetCachedPath(cachedFile);
184   if (CFile::Exists(path))
185     CFile::Delete(path);
186   path = URIUtils::ReplaceExtension(path, ".dds");
187   if (CFile::Exists(path))
188     CFile::Delete(path);
189 }
190
191 bool CTextureCache::ClearCachedImage(int id)
192 {
193   CStdString cachedFile;
194   if (ClearCachedTexture(id, cachedFile))
195   {
196     cachedFile = GetCachedPath(cachedFile);
197     if (CFile::Exists(cachedFile))
198       CFile::Delete(cachedFile);
199     cachedFile = URIUtils::ReplaceExtension(cachedFile, ".dds");
200     if (CFile::Exists(cachedFile))
201       CFile::Delete(cachedFile);
202     return true;
203   }
204   return false;
205 }
206
207 bool CTextureCache::GetCachedTexture(const CStdString &url, CTextureDetails &details)
208 {
209   CSingleLock lock(m_databaseSection);
210   return m_database.GetCachedTexture(url, details);
211 }
212
213 bool CTextureCache::AddCachedTexture(const CStdString &url, const CTextureDetails &details)
214 {
215   CSingleLock lock(m_databaseSection);
216   return m_database.AddCachedTexture(url, details);
217 }
218
219 void CTextureCache::IncrementUseCount(const CTextureDetails &details)
220 {
221   static const size_t count_before_update = 100;
222   CSingleLock lock(m_useCountSection);
223   m_useCounts.reserve(count_before_update);
224   m_useCounts.push_back(details);
225   if (m_useCounts.size() >= count_before_update)
226   {
227     AddJob(new CTextureUseCountJob(m_useCounts));
228     m_useCounts.clear();
229   }
230 }
231
232 bool CTextureCache::SetCachedTextureValid(const CStdString &url, bool updateable)
233 {
234   CSingleLock lock(m_databaseSection);
235   return m_database.SetCachedTextureValid(url, updateable);
236 }
237
238 bool CTextureCache::ClearCachedTexture(const CStdString &url, CStdString &cachedURL)
239 {
240   CSingleLock lock(m_databaseSection);
241   return m_database.ClearCachedTexture(url, cachedURL);
242 }
243
244 bool CTextureCache::ClearCachedTexture(int id, CStdString &cachedURL)
245 {
246   CSingleLock lock(m_databaseSection);
247   return m_database.ClearCachedTexture(id, cachedURL);
248 }
249
250 CStdString CTextureCache::GetCacheFile(const CStdString &url)
251 {
252   Crc32 crc;
253   crc.ComputeFromLowerCase(url);
254   CStdString hex;
255   hex.Format("%08x", (unsigned int)crc);
256   CStdString hash;
257   hash.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_processing.find(job->m_url);
279     if (i != m_processing.end())
280       m_processing.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_processing.find(cacheJob->m_url);
305       if (i == m_processing.end())
306       {
307         m_processing.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.IsEmpty())
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.IsEmpty())
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 }