2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
9 #include "TextureCache.h"
11 #include "ServiceBroker.h"
12 #include "TextureCacheJob.h"
14 #include "commons/ilog.h"
15 #include "filesystem/File.h"
16 #include "filesystem/IFileTypes.h"
17 #include "guilib/Texture.h"
18 #include "profiles/ProfileManager.h"
19 #include "settings/SettingsComponent.h"
20 #include "utils/Crc32.h"
21 #include "utils/Job.h"
22 #include "utils/StringUtils.h"
23 #include "utils/URIUtils.h"
24 #include "utils/log.h"
31 using namespace XFILE
;
32 using namespace std::chrono_literals
;
34 CTextureCache::CTextureCache() : CJobQueue(false, 1, CJob::PRIORITY_LOW_PAUSABLE
)
38 CTextureCache::~CTextureCache() = default;
40 void CTextureCache::Initialize()
42 std::unique_lock
<CCriticalSection
> lock(m_databaseSection
);
43 if (!m_database
.IsOpen())
47 void CTextureCache::Deinitialize()
51 std::unique_lock
<CCriticalSection
> lock(m_databaseSection
);
55 bool CTextureCache::IsCachedImage(const std::string
&url
) const
60 if (!CURL::IsFullPath(url
))
63 const std::shared_ptr
<CProfileManager
> profileManager
= CServiceBroker::GetSettingsComponent()->GetProfileManager();
65 return URIUtils::PathHasParent(url
, "special://skin", true) ||
66 URIUtils::PathHasParent(url
, "special://temp", true) ||
67 URIUtils::PathHasParent(url
, "resource://", true) ||
68 URIUtils::PathHasParent(url
, "androidapp://", true) ||
69 URIUtils::PathHasParent(url
, profileManager
->GetThumbnailsFolder(), true);
72 bool CTextureCache::HasCachedImage(const std::string
&url
)
74 CTextureDetails details
;
75 std::string
cachedImage(GetCachedImage(url
, details
));
76 return (!cachedImage
.empty() && cachedImage
!= url
);
79 std::string
CTextureCache::GetCachedImage(const std::string
&image
, CTextureDetails
&details
, bool trackUsage
)
81 std::string url
= CTextureUtils::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 bool CTextureCache::CanCacheImageURL(const CURL
&url
)
99 return url
.GetUserName().empty() || url
.GetUserName() == "music" ||
100 StringUtils::StartsWith(url
.GetUserName(), "video_") ||
101 StringUtils::StartsWith(url
.GetUserName(), "pvr") ||
102 StringUtils::StartsWith(url
.GetUserName(), "epg");
105 std::string
CTextureCache::CheckCachedImage(const std::string
&url
, bool &needsRecaching
)
107 CTextureDetails details
;
108 std::string
path(GetCachedImage(url
, details
, true));
109 needsRecaching
= !details
.hash
.empty();
115 void CTextureCache::BackgroundCacheImage(const std::string
&url
)
120 CTextureDetails details
;
121 std::string
path(GetCachedImage(url
, details
));
122 if (!path
.empty() && details
.hash
.empty())
123 return; // image is already cached and doesn't need to be checked further
125 path
= CTextureUtils::UnwrapImageURL(url
);
130 AddJob(new CTextureCacheJob(path
, details
.hash
));
133 bool CTextureCache::StartCacheImage(const std::string
& image
)
135 std::unique_lock
<CCriticalSection
> lock(m_processingSection
);
136 std::set
<std::string
>::iterator i
= m_processinglist
.find(image
);
137 if (i
== m_processinglist
.end())
139 m_processinglist
.insert(image
);
145 std::string
CTextureCache::CacheImage(const std::string
& image
,
146 std::unique_ptr
<CTexture
>* texture
/*= nullptr*/,
147 CTextureDetails
* details
/*= nullptr*/)
149 std::string url
= CTextureUtils::UnwrapImageURL(image
);
153 std::unique_lock
<CCriticalSection
> lock(m_processingSection
);
154 if (m_processinglist
.find(url
) == m_processinglist
.end())
156 m_processinglist
.insert(url
);
158 // cache the texture directly
159 CTextureCacheJob
job(url
);
160 bool success
= job
.CacheTexture(texture
);
161 OnCachingComplete(success
, &job
);
162 if (success
&& details
)
163 *details
= job
.m_details
;
164 return success
? GetCachedPath(job
.m_details
.file
) : "";
168 // wait for currently processing job to end.
171 m_completeEvent
.Wait(1000ms
);
173 std::unique_lock
<CCriticalSection
> lock(m_processingSection
);
174 if (m_processinglist
.find(url
) == m_processinglist
.end())
178 CTextureDetails tempDetails
;
180 details
= &tempDetails
;
182 std::string cachedpath
= GetCachedImage(url
, *details
, true);
183 if (!cachedpath
.empty())
186 *texture
= CTexture::LoadFromFile(cachedpath
, 0, 0);
190 CLog::Log(LOGDEBUG
, "CTextureCache::{} - Return NULL texture because cache is not ready",
197 bool CTextureCache::CacheImage(const std::string
&image
, CTextureDetails
&details
)
199 std::string path
= GetCachedImage(image
, details
);
200 if (path
.empty()) // not cached
201 path
= CacheImage(image
, NULL
, &details
);
203 return !path
.empty();
206 void CTextureCache::ClearCachedImage(const std::string
&url
, bool deleteSource
/*= false */)
208 //! @todo This can be removed when the texture cache covers everything.
209 std::string path
= deleteSource
? url
: "";
210 std::string cachedFile
;
211 if (ClearCachedTexture(url
, cachedFile
))
212 path
= GetCachedPath(cachedFile
);
213 if (CFile::Exists(path
))
215 path
= URIUtils::ReplaceExtension(path
, ".dds");
216 if (CFile::Exists(path
))
220 bool CTextureCache::ClearCachedImage(int id
)
222 std::string cachedFile
;
223 if (ClearCachedTexture(id
, cachedFile
))
225 cachedFile
= GetCachedPath(cachedFile
);
226 if (CFile::Exists(cachedFile
))
227 CFile::Delete(cachedFile
);
228 cachedFile
= URIUtils::ReplaceExtension(cachedFile
, ".dds");
229 if (CFile::Exists(cachedFile
))
230 CFile::Delete(cachedFile
);
236 bool CTextureCache::GetCachedTexture(const std::string
&url
, CTextureDetails
&details
)
238 std::unique_lock
<CCriticalSection
> lock(m_databaseSection
);
239 return m_database
.GetCachedTexture(url
, details
);
242 bool CTextureCache::AddCachedTexture(const std::string
&url
, const CTextureDetails
&details
)
244 std::unique_lock
<CCriticalSection
> lock(m_databaseSection
);
245 return m_database
.AddCachedTexture(url
, details
);
248 void CTextureCache::IncrementUseCount(const CTextureDetails
&details
)
250 static const size_t count_before_update
= 100;
251 std::unique_lock
<CCriticalSection
> lock(m_useCountSection
);
252 m_useCounts
.reserve(count_before_update
);
253 m_useCounts
.push_back(details
);
254 if (m_useCounts
.size() >= count_before_update
)
256 AddJob(new CTextureUseCountJob(m_useCounts
));
261 bool CTextureCache::SetCachedTextureValid(const std::string
&url
, bool updateable
)
263 std::unique_lock
<CCriticalSection
> lock(m_databaseSection
);
264 return m_database
.SetCachedTextureValid(url
, updateable
);
267 bool CTextureCache::ClearCachedTexture(const std::string
&url
, std::string
&cachedURL
)
269 std::unique_lock
<CCriticalSection
> lock(m_databaseSection
);
270 return m_database
.ClearCachedTexture(url
, cachedURL
);
273 bool CTextureCache::ClearCachedTexture(int id
, std::string
&cachedURL
)
275 std::unique_lock
<CCriticalSection
> lock(m_databaseSection
);
276 return m_database
.ClearCachedTexture(id
, cachedURL
);
279 std::string
CTextureCache::GetCacheFile(const std::string
&url
)
281 auto crc
= Crc32::ComputeFromLowerCase(url
);
282 std::string hex
= StringUtils::Format("{:08x}", crc
);
283 std::string hash
= StringUtils::Format("{}/{}", hex
[0], hex
.c_str());
287 std::string
CTextureCache::GetCachedPath(const std::string
&file
)
289 const std::shared_ptr
<CProfileManager
> profileManager
= CServiceBroker::GetSettingsComponent()->GetProfileManager();
291 return URIUtils::AddFileToFolder(profileManager
->GetThumbnailsFolder(), file
);
294 void CTextureCache::OnCachingComplete(bool success
, CTextureCacheJob
*job
)
298 if (job
->m_oldHash
== job
->m_details
.hash
)
299 SetCachedTextureValid(job
->m_url
, job
->m_details
.updateable
);
301 AddCachedTexture(job
->m_url
, job
->m_details
);
304 { // remove from our processing list
305 std::unique_lock
<CCriticalSection
> lock(m_processingSection
);
306 std::set
<std::string
>::iterator i
= m_processinglist
.find(job
->m_url
);
307 if (i
!= m_processinglist
.end())
308 m_processinglist
.erase(i
);
311 m_completeEvent
.Set();
314 void CTextureCache::OnJobComplete(unsigned int jobID
, bool success
, CJob
*job
)
316 if (strcmp(job
->GetType(), kJobTypeCacheImage
) == 0)
317 OnCachingComplete(success
, static_cast<CTextureCacheJob
*>(job
));
318 return CJobQueue::OnJobComplete(jobID
, success
, job
);
321 bool CTextureCache::Export(const std::string
&image
, const std::string
&destination
, bool overwrite
)
323 CTextureDetails details
;
324 std::string
cachedImage(GetCachedImage(image
, details
));
325 if (!cachedImage
.empty())
327 std::string dest
= destination
+ URIUtils::GetExtension(cachedImage
);
328 if (overwrite
|| !CFile::Exists(dest
))
330 if (CFile::Copy(cachedImage
, dest
))
332 CLog::Log(LOGERROR
, "{} failed exporting '{}' to '{}'", __FUNCTION__
, cachedImage
, dest
);
338 bool CTextureCache::Export(const std::string
&image
, const std::string
&destination
)
340 CTextureDetails details
;
341 std::string
cachedImage(GetCachedImage(image
, details
));
342 if (!cachedImage
.empty())
344 if (CFile::Copy(cachedImage
, destination
))
346 CLog::Log(LOGERROR
, "{} failed exporting '{}' to '{}'", __FUNCTION__
, cachedImage
, destination
);