2 * Copyright (C) 2012-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 "TextureCacheJob.h"
12 #include "ServiceBroker.h"
13 #include "TextureCache.h"
14 #include "TextureDatabase.h"
16 #include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/audiodecoder.h"
17 #include "commons/ilog.h"
18 #include "filesystem/File.h"
19 #include "guilib/Texture.h"
20 #include "imagefiles/SpecialImageLoaderFactory.h"
21 #include "pictures/Picture.h"
22 #include "settings/AdvancedSettings.h"
23 #include "settings/SettingsComponent.h"
24 #include "utils/StringUtils.h"
25 #include "utils/URIUtils.h"
26 #include "utils/log.h"
33 #include "PlatformDefs.h"
35 CTextureCacheJob::CTextureCacheJob(const std::string
&url
, const std::string
&oldHash
):
38 m_cachePath(CTextureCache::GetCacheFile(m_url
))
42 CTextureCacheJob::~CTextureCacheJob() = default;
44 bool CTextureCacheJob::operator==(const CJob
* job
) const
46 if (strcmp(job
->GetType(),GetType()) == 0)
48 const CTextureCacheJob
* cacheJob
= dynamic_cast<const CTextureCacheJob
*>(job
);
49 if (cacheJob
&& cacheJob
->m_cachePath
== m_cachePath
)
55 bool CTextureCacheJob::DoWork()
57 if (ShouldCancel(0, 0))
60 // check whether we need cache the job anyway
61 bool needsRecaching
= false;
62 std::string
path(CServiceBroker::GetTextureCache()->CheckCachedImage(m_url
, needsRecaching
));
63 if (!path
.empty() && !needsRecaching
)
65 if (CServiceBroker::GetTextureCache()->StartCacheImage(m_url
))
66 return CacheTexture();
73 // Most PVR images use "additional_info" to signify 'ownership' of basic images for easy
74 // cache cleaning, rather than special generated images
75 bool IsPVROwnedImage(const std::string
& additional_info
)
77 return additional_info
== "pvrchannel_radio" || additional_info
== "pvrchannel_tv" ||
78 additional_info
== "pvrprovider" || additional_info
== "pvrrecording" ||
79 StringUtils::StartsWith(additional_info
, "epgtag_");
82 // DecodeImageURL can also set "additional_info" to 'flipped' for mirror images selected in
83 // the GUI, so is not a special generated image
84 bool IsControl(const std::string
& additional_info
)
86 return additional_info
== "flipped";
89 // special generated images and images served via HTTP should not be regularly checked for changes
90 bool ShouldCheckForChanges(const std::string
& additional_info
, const std::string
& url
)
92 const bool isSpecialImage
=
93 !additional_info
.empty() && !IsControl(additional_info
) && !IsPVROwnedImage(additional_info
);
98 StringUtils::StartsWith(url
, "http://") || StringUtils::StartsWith(url
, "https://");
103 bool CTextureCacheJob::CacheTexture(std::unique_ptr
<CTexture
>* out_texture
)
105 // unwrap the URL as required
106 std::string additional_info
;
107 unsigned int width
, height
;
108 CPictureScalingAlgorithm::Algorithm scalingAlgorithm
;
109 std::string image
= DecodeImageURL(m_url
, width
, height
, scalingAlgorithm
, additional_info
);
111 m_details
.updateable
= ShouldCheckForChanges(additional_info
, image
);
113 if (m_details
.updateable
)
116 m_details
.hash
= GetImageHash(image
);
117 if (m_details
.hash
.empty())
120 if (m_details
.hash
== m_oldHash
)
122 m_details
.hashRevalidated
= true;
127 std::unique_ptr
<CTexture
> texture
= LoadImage(image
, width
, height
, additional_info
, true);
130 if (texture
->HasAlpha())
131 m_details
.file
= m_cachePath
+ ".png";
133 m_details
.file
= m_cachePath
+ ".jpg";
135 CLog::Log(LOGDEBUG
, "{} image '{}' to '{}':", m_oldHash
.empty() ? "Caching" : "Recaching",
136 CURL::GetRedacted(image
), m_details
.file
);
138 if (CPicture::CacheTexture(texture
.get(), width
, height
,
139 CTextureCache::GetCachedPath(m_details
.file
), scalingAlgorithm
))
141 m_details
.width
= width
;
142 m_details
.height
= height
;
143 if (out_texture
) // caller wants the texture
144 *out_texture
= std::move(texture
);
151 bool CTextureCacheJob::ResizeTexture(const std::string
&url
, uint8_t* &result
, size_t &result_size
)
159 // unwrap the URL as required
160 std::string additional_info
;
161 unsigned int width
, height
;
162 CPictureScalingAlgorithm::Algorithm scalingAlgorithm
;
163 std::string image
= DecodeImageURL(url
, width
, height
, scalingAlgorithm
, additional_info
);
167 std::unique_ptr
<CTexture
> texture
= LoadImage(image
, width
, height
, additional_info
, true);
171 bool success
= CPicture::ResizeTexture(image
, texture
.get(), width
, height
, result
, result_size
,
177 std::string
CTextureCacheJob::DecodeImageURL(const std::string
&url
, unsigned int &width
, unsigned int &height
, CPictureScalingAlgorithm::Algorithm
& scalingAlgorithm
, std::string
&additional_info
)
179 // unwrap the URL as required
180 std::string
image(url
);
181 additional_info
.clear();
183 scalingAlgorithm
= CPictureScalingAlgorithm::NoAlgorithm
;
184 if (StringUtils::StartsWith(url
, "image://"))
186 // format is image://[type@]<url_encoded_path>?options
189 if (!CTextureCache::CanCacheImageURL(thumbURL
))
191 if (thumbURL
.GetUserName() == "music" || thumbURL
.GetUserName() == "video" ||
192 thumbURL
.GetUserName() == "picturefolder")
193 additional_info
= thumbURL
.GetUserName();
194 if (StringUtils::StartsWith(thumbURL
.GetUserName(), "video_") ||
195 StringUtils::StartsWith(thumbURL
.GetUserName(), "pvr") ||
196 StringUtils::StartsWith(thumbURL
.GetUserName(), "epg"))
197 additional_info
= thumbURL
.GetUserName();
199 image
= thumbURL
.GetHostName();
201 if (thumbURL
.HasOption("flipped"))
202 additional_info
= "flipped";
204 if (thumbURL
.GetOption("size") == "thumb")
205 width
= height
= CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_imageRes
;
208 if (thumbURL
.HasOption("width") && StringUtils::IsInteger(thumbURL
.GetOption("width")))
209 width
= strtol(thumbURL
.GetOption("width").c_str(), NULL
, 0);
210 if (thumbURL
.HasOption("height") && StringUtils::IsInteger(thumbURL
.GetOption("height")))
211 height
= strtol(thumbURL
.GetOption("height").c_str(), NULL
, 0);
214 if (thumbURL
.HasOption("scaling_algorithm"))
215 scalingAlgorithm
= CPictureScalingAlgorithm::FromString(thumbURL
.GetOption("scaling_algorithm"));
218 if (StringUtils::StartsWith(url
, "chapter://"))
220 // workaround for chapter thumbnail paths, which don't yet conform to the image:// path.
221 additional_info
= "videochapter";
224 // Handle special case about audiodecoder addon music files, e.g. SACD
225 if (StringUtils::EndsWith(URIUtils::GetExtension(image
), KODI_ADDON_AUDIODECODER_TRACK_EXT
))
227 std::string addonImageURL
= URIUtils::GetDirectory(image
);
228 URIUtils::RemoveSlashAtEnd(addonImageURL
);
229 if (XFILE::CFile::Exists(addonImageURL
))
230 image
= addonImageURL
;
236 std::unique_ptr
<CTexture
> CTextureCacheJob::LoadImage(const std::string
& image
,
239 const std::string
& additional_info
,
242 if (!additional_info
.empty())
244 IMAGE_FILES::CSpecialImageLoaderFactory specialImageLoader
{};
245 auto texture
= specialImageLoader
.Load(additional_info
, image
, width
, height
);
250 // Validate file URL to see if it is an image
251 CFileItem
file(image
, false);
252 file
.FillInMimeType();
253 if (!(file
.IsPicture() && !(file
.IsZIP() || file
.IsRAR() || file
.IsCBR() || file
.IsCBZ() ))
254 && !StringUtils::StartsWithNoCase(file
.GetMimeType(), "image/") && !StringUtils::EqualsNoCase(file
.GetMimeType(), "application/octet-stream")) // ignore non-pictures
257 std::unique_ptr
<CTexture
> texture
=
258 CTexture::LoadFromFile(image
, width
, height
, requirePixels
, file
.GetMimeType());
262 // EXIF bits are interpreted as: <flipXY><flipY*flipX><flipX>
263 // where to undo the operation we apply them in reverse order <flipX>*<flipY*flipX>*<flipXY>
264 // When flipped we have an additional <flipX> on the left, which is equivalent to toggling the last bit
265 if (additional_info
== "flipped")
266 texture
->SetOrientation(texture
->GetOrientation() ^ 1);
271 std::string
CTextureCacheJob::GetImageHash(const std::string
&url
)
273 // silently ignore - we cannot stat these
274 // in the case of upnp thumbs are/should be provided when filling the directory list, there's no reason to stat all object ids
275 if (URIUtils::IsProtocol(url
, "addons") || URIUtils::IsProtocol(url
, "plugin") ||
276 URIUtils::IsProtocol(url
, "upnp"))
280 if (XFILE::CFile::Stat(url
, &st
) == 0)
282 int64_t time
= st
.st_mtime
;
285 if (time
|| st
.st_size
)
286 return StringUtils::Format("d{}s{}", time
, st
.st_size
);
288 // the image exists but we couldn't determine the mtime/ctime and/or size
289 // so set an obviously bad hash
293 CLog::Log(LOGDEBUG
, "{} - unable to stat url {}", __FUNCTION__
, CURL::GetRedacted(url
));
297 CTextureUseCountJob::CTextureUseCountJob(const std::vector
<CTextureDetails
> &textures
) : m_textures(textures
)
301 bool CTextureUseCountJob::operator==(const CJob
* job
) const
303 if (strcmp(job
->GetType(),GetType()) == 0)
305 const CTextureUseCountJob
* useJob
= dynamic_cast<const CTextureUseCountJob
*>(job
);
306 if (useJob
&& useJob
->m_textures
== m_textures
)
312 bool CTextureUseCountJob::DoWork()
317 db
.BeginTransaction();
318 for (std::vector
<CTextureDetails
>::const_iterator i
= m_textures
.begin(); i
!= m_textures
.end(); ++i
)
319 db
.IncrementUseCount(*i
);
320 db
.CommitTransaction();