2 * Copyright (C) 2012 Team XBMC
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)
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.
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/>.
21 #include "TextureCacheJob.h"
22 #include "TextureCache.h"
23 #include "guilib/Texture.h"
24 #include "guilib/DDSImage.h"
25 #include "settings/Settings.h"
26 #include "settings/AdvancedSettings.h"
27 #include "settings/GUISettings.h"
28 #include "utils/log.h"
29 #include "filesystem/File.h"
30 #include "pictures/Picture.h"
31 #include "utils/URIUtils.h"
32 #include "utils/StringUtils.h"
35 #include "music/MusicThumbLoader.h"
36 #include "music/tags/MusicInfoTag.h"
38 CTextureCacheJob::CTextureCacheJob(const CStdString &url, const CStdString &oldHash)
42 m_cachePath = CTextureCache::GetCacheFile(m_url);
45 CTextureCacheJob::~CTextureCacheJob()
49 bool CTextureCacheJob::operator==(const CJob* job) const
51 if (strcmp(job->GetType(),GetType()) == 0)
53 const CTextureCacheJob* cacheJob = dynamic_cast<const CTextureCacheJob*>(job);
54 if (cacheJob && cacheJob->m_cachePath == m_cachePath)
60 bool CTextureCacheJob::DoWork()
62 if (ShouldCancel(0, 0))
64 if (ShouldCancel(1, 0)) // HACK: second check is because we cancel the job in the first callback, but we don't detect it
65 return false; // until the second
67 // check whether we need cache the job anyway
68 bool needsRecaching = false;
69 CStdString path(CTextureCache::Get().CheckCachedImage(m_url, false, needsRecaching));
70 if (!path.IsEmpty() && !needsRecaching)
72 return CacheTexture();
75 bool CTextureCacheJob::CacheTexture(CBaseTexture **out_texture)
77 // unwrap the URL as required
78 std::string additional_info;
79 unsigned int width, height;
80 CStdString image = DecodeImageURL(m_url, width, height, additional_info);
82 m_details.updateable = additional_info != "music" && UpdateableURL(image);
85 m_details.hash = GetImageHash(image);
86 if (m_details.hash.empty())
88 else if (m_details.hash == m_oldHash)
91 CBaseTexture *texture = LoadImage(image, width, height, additional_info);
94 if (texture->HasAlpha())
95 m_details.file = m_cachePath + ".png";
97 m_details.file = m_cachePath + ".jpg";
99 CLog::Log(LOGDEBUG, "%s image '%s' to '%s':", m_oldHash.IsEmpty() ? "Caching" : "Recaching", image.c_str(), m_details.file.c_str());
101 if (CPicture::CacheTexture(texture, width, height, CTextureCache::GetCachedPath(m_details.file)))
103 m_details.width = width;
104 m_details.height = height;
105 if (out_texture) // caller wants the texture
106 *out_texture = texture;
116 CStdString CTextureCacheJob::DecodeImageURL(const CStdString &url, unsigned int &width, unsigned int &height, std::string &additional_info)
118 // unwrap the URL as required
119 CStdString image(url);
120 additional_info.clear();
122 if (url.compare(0, 8, "image://") == 0)
124 // format is image://[type@]<url_encoded_path>?options
127 if (!CTextureCache::CanCacheImageURL(thumbURL))
129 if (thumbURL.GetUserName() == "music")
130 additional_info = "music";
132 image = thumbURL.GetHostName();
134 CStdString optionString = thumbURL.GetOptions().Mid(1);
135 optionString.TrimRight('/'); // in case XBMC adds a slash
137 std::vector<CStdString> options;
138 StringUtils::SplitString(optionString, "&", options);
139 for (std::vector<CStdString>::iterator i = options.begin(); i != options.end(); i++)
141 CStdString option, value;
142 int pos = i->Find('=');
145 option = i->Left(pos);
146 value = i->Mid(pos + 1);
153 if (option == "size" && value == "thumb")
155 width = height = g_advancedSettings.GetThumbSize();
157 else if (option == "flipped")
159 additional_info = "flipped";
166 CBaseTexture *CTextureCacheJob::LoadImage(const CStdString &image, unsigned int width, unsigned int height, const std::string &additional_info)
168 if (additional_info == "music")
169 { // special case for embedded music images
170 MUSIC_INFO::EmbeddedArt art;
171 if (CMusicThumbLoader::GetEmbeddedThumb(image, art))
172 return CBaseTexture::LoadFromFileInMemory(&art.data[0], art.size, art.mime, width, height);
175 // Validate file URL to see if it is an image
176 CFileItem file(image, false);
177 if (!(file.IsPicture() && !(file.IsZIP() || file.IsRAR() || file.IsCBR() || file.IsCBZ() ))
178 && !file.GetMimeType().Left(6).Equals("image/") && !file.GetMimeType().Equals("application/octet-stream")) // ignore non-pictures
181 CBaseTexture *texture = CBaseTexture::LoadFromFile(image, width, height, g_guiSettings.GetBool("pictures.useexifrotation"));
185 // EXIF bits are interpreted as: <flipXY><flipY*flipX><flipX>
186 // where to undo the operation we apply them in reverse order <flipX>*<flipY*flipX>*<flipXY>
187 // When flipped we have an additional <flipX> on the left, which is equivalent to toggling the last bit
188 if (additional_info == "flipped")
189 texture->SetOrientation(texture->GetOrientation() ^ 1);
194 bool CTextureCacheJob::UpdateableURL(const CStdString &url) const
196 // we don't constantly check online images
197 if (url.compare(0, 7, "http://") == 0 ||
198 url.compare(0, 8, "https://") == 0)
203 CStdString CTextureCacheJob::GetImageHash(const CStdString &url)
206 if (XFILE::CFile::Stat(url, &st) == 0)
208 int64_t time = st.st_mtime;
211 if (time || st.st_size)
214 hash.Format("d%"PRId64"s%"PRId64, time, st.st_size);
218 CLog::Log(LOGDEBUG, "%s - unable to stat url %s", __FUNCTION__, url.c_str());
222 CTextureDDSJob::CTextureDDSJob(const CStdString &original)
224 m_original = original;
227 bool CTextureDDSJob::operator==(const CJob* job) const
229 if (strcmp(job->GetType(),GetType()) == 0)
231 const CTextureDDSJob* ddsJob = dynamic_cast<const CTextureDDSJob*>(job);
232 if (ddsJob && ddsJob->m_original == m_original)
238 bool CTextureDDSJob::DoWork()
240 if (URIUtils::GetExtension(m_original).Equals(".dds"))
242 CBaseTexture *texture = CBaseTexture::LoadFromFile(m_original);
246 CLog::Log(LOGDEBUG, "Creating DDS version of: %s", m_original.c_str());
247 bool ret = dds.Create(URIUtils::ReplaceExtension(m_original, ".dds"), texture->GetWidth(), texture->GetHeight(), texture->GetPitch(), texture->GetPixels(), 40);
254 CTextureUseCountJob::CTextureUseCountJob(const std::vector<CTextureDetails> &textures) : m_textures(textures)
258 bool CTextureUseCountJob::operator==(const CJob* job) const
260 if (strcmp(job->GetType(),GetType()) == 0)
262 const CTextureUseCountJob* useJob = dynamic_cast<const CTextureUseCountJob*>(job);
263 if (useJob && useJob->m_textures == m_textures)
269 bool CTextureUseCountJob::DoWork()
274 db.BeginTransaction();
275 for (std::vector<CTextureDetails>::const_iterator i = m_textures.begin(); i != m_textures.end(); ++i)
276 db.IncrementUseCount(*i);
277 db.CommitTransaction();