Merge pull request #3256 from perpe/droid-x86
[vuplus_xbmc] / xbmc / TextureCacheJob.cpp
1 /*
2  *      Copyright (C) 2012-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 "TextureCacheJob.h"
22 #include "TextureCache.h"
23 #include "guilib/Texture.h"
24 #include "guilib/DDSImage.h"
25 #include "settings/AdvancedSettings.h"
26 #include "settings/Settings.h"
27 #include "utils/log.h"
28 #include "filesystem/File.h"
29 #include "pictures/Picture.h"
30 #include "utils/URIUtils.h"
31 #include "utils/StringUtils.h"
32 #include "URL.h"
33 #include "FileItem.h"
34 #include "music/MusicThumbLoader.h"
35 #include "music/tags/MusicInfoTag.h"
36
37 CTextureCacheJob::CTextureCacheJob(const CStdString &url, const CStdString &oldHash)
38 {
39   m_url = url;
40   m_oldHash = oldHash;
41   m_cachePath = CTextureCache::GetCacheFile(m_url);
42 }
43
44 CTextureCacheJob::~CTextureCacheJob()
45 {
46 }
47
48 bool CTextureCacheJob::operator==(const CJob* job) const
49 {
50   if (strcmp(job->GetType(),GetType()) == 0)
51   {
52     const CTextureCacheJob* cacheJob = dynamic_cast<const CTextureCacheJob*>(job);
53     if (cacheJob && cacheJob->m_cachePath == m_cachePath)
54       return true;
55   }
56   return false;
57 }
58
59 bool CTextureCacheJob::DoWork()
60 {
61   if (ShouldCancel(0, 0))
62     return false;
63   if (ShouldCancel(1, 0)) // HACK: second check is because we cancel the job in the first callback, but we don't detect it
64     return false;         //       until the second
65
66   // check whether we need cache the job anyway
67   bool needsRecaching = false;
68   CStdString path(CTextureCache::Get().CheckCachedImage(m_url, false, needsRecaching));
69   if (!path.IsEmpty() && !needsRecaching)
70     return false;
71   return CacheTexture();
72 }
73
74 bool CTextureCacheJob::CacheTexture(CBaseTexture **out_texture)
75 {
76   // unwrap the URL as required
77   std::string additional_info;
78   unsigned int width, height;
79   CStdString image = DecodeImageURL(m_url, width, height, additional_info);
80
81   m_details.updateable = additional_info != "music" && UpdateableURL(image);
82
83   // generate the hash
84   m_details.hash = GetImageHash(image);
85   if (m_details.hash.empty())
86     return false;
87   else if (m_details.hash == m_oldHash)
88     return true;
89
90   CBaseTexture *texture = LoadImage(image, width, height, additional_info);
91   if (texture)
92   {
93     if (texture->HasAlpha())
94       m_details.file = m_cachePath + ".png";
95     else
96       m_details.file = m_cachePath + ".jpg";
97
98     CLog::Log(LOGDEBUG, "%s image '%s' to '%s':", m_oldHash.IsEmpty() ? "Caching" : "Recaching", image.c_str(), m_details.file.c_str());
99
100     if (CPicture::CacheTexture(texture, width, height, CTextureCache::GetCachedPath(m_details.file)))
101     {
102       m_details.width = width;
103       m_details.height = height;
104       if (out_texture) // caller wants the texture
105         *out_texture = texture;
106       else
107         delete texture;
108       return true;
109     }
110   }
111   delete texture;
112   return false;
113 }
114
115 CStdString CTextureCacheJob::DecodeImageURL(const CStdString &url, unsigned int &width, unsigned int &height, std::string &additional_info)
116 {
117   // unwrap the URL as required
118   CStdString image(url);
119   additional_info.clear();
120   width = height = 0;
121   if (StringUtils::StartsWith(url, "image://"))
122   {
123     // format is image://[type@]<url_encoded_path>?options
124     CURL thumbURL(url);
125
126     if (!CTextureCache::CanCacheImageURL(thumbURL))
127       return "";
128     if (thumbURL.GetUserName() == "music")
129       additional_info = "music";
130
131     image = thumbURL.GetHostName();
132
133     CStdString optionString = thumbURL.GetOptions().Mid(1);
134     optionString.TrimRight('/'); // in case XBMC adds a slash
135
136     std::vector<CStdString> options;
137     StringUtils::SplitString(optionString, "&", options);
138     for (std::vector<CStdString>::iterator i = options.begin(); i != options.end(); i++)
139     {
140       CStdString option, value;
141       int pos = i->Find('=');
142       if (pos != -1)
143       {
144         option = i->Left(pos);
145         value  = i->Mid(pos + 1);
146       }
147       else
148       {
149         option = *i;
150         value = "";
151       }
152       if (option == "size" && value == "thumb")
153       {
154         width = height = g_advancedSettings.GetThumbSize();
155       }
156       else if (option == "flipped")
157       {
158         additional_info = "flipped";
159       }
160     }
161   }
162   return image;
163 }
164
165 CBaseTexture *CTextureCacheJob::LoadImage(const CStdString &image, unsigned int width, unsigned int height, const std::string &additional_info)
166 {
167   if (additional_info == "music")
168   { // special case for embedded music images
169     MUSIC_INFO::EmbeddedArt art;
170     if (CMusicThumbLoader::GetEmbeddedThumb(image, art))
171       return CBaseTexture::LoadFromFileInMemory(&art.data[0], art.size, art.mime, width, height);
172   }
173
174   // Validate file URL to see if it is an image
175   CFileItem file(image, false);
176   file.FillInMimeType();
177   if (!(file.IsPicture() && !(file.IsZIP() || file.IsRAR() || file.IsCBR() || file.IsCBZ() ))
178       && !StringUtils::StartsWithNoCase(file.GetMimeType(), "image/") && !file.GetMimeType().Equals("application/octet-stream")) // ignore non-pictures
179     return NULL;
180
181   CBaseTexture *texture = CBaseTexture::LoadFromFile(image, width, height, CSettings::Get().GetBool("pictures.useexifrotation"));
182   if (!texture)
183     return NULL;
184
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);
190
191   return texture;
192 }
193
194 bool CTextureCacheJob::UpdateableURL(const CStdString &url) const
195 {
196   // we don't constantly check online images
197   if (StringUtils::StartsWith(url, "http://") ||
198       StringUtils::StartsWith(url, "https://"))
199     return false;
200   return true;
201 }
202
203 CStdString CTextureCacheJob::GetImageHash(const CStdString &url)
204 {
205   struct __stat64 st;
206   if (XFILE::CFile::Stat(url, &st) == 0)
207   {
208     int64_t time = st.st_mtime;
209     if (!time)
210       time = st.st_ctime;
211     if (time || st.st_size)
212     {
213       CStdString hash;
214       hash.Format("d%"PRId64"s%"PRId64, time, st.st_size);
215       return hash;
216     }
217   }
218   CLog::Log(LOGDEBUG, "%s - unable to stat url %s", __FUNCTION__, url.c_str());
219   return "";
220 }
221
222 CTextureDDSJob::CTextureDDSJob(const CStdString &original)
223 {
224   m_original = original;
225 }
226
227 bool CTextureDDSJob::operator==(const CJob* job) const
228 {
229   if (strcmp(job->GetType(),GetType()) == 0)
230   {
231     const CTextureDDSJob* ddsJob = dynamic_cast<const CTextureDDSJob*>(job);
232     if (ddsJob && ddsJob->m_original == m_original)
233       return true;
234   }
235   return false;
236 }
237
238 bool CTextureDDSJob::DoWork()
239 {
240   if (URIUtils::HasExtension(m_original, ".dds"))
241     return false;
242   CBaseTexture *texture = CBaseTexture::LoadFromFile(m_original);
243   if (texture)
244   { // convert to DDS
245     CDDSImage dds;
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);
248     delete texture;
249     return ret;
250   }
251   return false;
252 }
253
254 CTextureUseCountJob::CTextureUseCountJob(const std::vector<CTextureDetails> &textures) : m_textures(textures)
255 {
256 }
257
258 bool CTextureUseCountJob::operator==(const CJob* job) const
259 {
260   if (strcmp(job->GetType(),GetType()) == 0)
261   {
262     const CTextureUseCountJob* useJob = dynamic_cast<const CTextureUseCountJob*>(job);
263     if (useJob && useJob->m_textures == m_textures)
264       return true;
265   }
266   return false;
267 }
268
269 bool CTextureUseCountJob::DoWork()
270 {
271   CTextureDatabase db;
272   if (db.Open())
273   {
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();
278   }
279   return true;
280 }