[win32] cmake: make use of ADDONS_TO_BUILD in make-addons.bat
[vuplus_xbmc] / xbmc / TextureDatabase.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 "TextureDatabase.h"
22 #include "utils/log.h"
23 #include "XBDateTime.h"
24 #include "dbwrappers/dataset.h"
25 #include "URL.h"
26 #include "utils/StringUtils.h"
27 #include "utils/Variant.h"
28
29 enum TextureField
30 {
31   TF_None = 0,
32   TF_Id,
33   TF_Url,
34   TF_CachedUrl,
35   TF_LastHashCheck,
36   TF_ImageHash,
37   TF_Width,
38   TF_Height,
39   TF_UseCount,
40   TF_LastUsed,
41   TF_Max
42 };
43
44 typedef struct
45 {
46   char string[14];
47   TextureField field;
48   CDatabaseQueryRule::FIELD_TYPE type;
49   bool browseable;
50   int localizedString;
51 } translateField;
52
53 static const translateField fields[] = {
54   { "none",          TF_None,          CDatabaseQueryRule::TEXT_FIELD    },
55   { "textureid",     TF_Id,            CDatabaseQueryRule::NUMERIC_FIELD },
56   { "url",           TF_Url,           CDatabaseQueryRule::TEXT_FIELD    },
57   { "cachedurl",     TF_CachedUrl,     CDatabaseQueryRule::TEXT_FIELD    },
58   { "lasthashcheck", TF_LastHashCheck, CDatabaseQueryRule::TEXT_FIELD    },
59   { "imagehash",     TF_ImageHash,     CDatabaseQueryRule::TEXT_FIELD    },
60   { "width",         TF_Width,         CDatabaseQueryRule::NUMERIC_FIELD },
61   { "height",        TF_Height,        CDatabaseQueryRule::NUMERIC_FIELD },
62   { "usecount",      TF_UseCount,      CDatabaseQueryRule::NUMERIC_FIELD },
63   { "lastused",      TF_LastUsed,      CDatabaseQueryRule::TEXT_FIELD    }
64 };
65
66 static const size_t NUM_FIELDS = sizeof(fields) / sizeof(translateField);
67
68 int CTextureRule::TranslateField(const char *field) const
69 {
70   for (unsigned int i = 0; i < NUM_FIELDS; i++)
71     if (StringUtils::EqualsNoCase(field, fields[i].string)) return fields[i].field;
72   return FieldNone;
73 }
74
75 std::string CTextureRule::TranslateField(int field) const
76 {
77   for (unsigned int i = 0; i < NUM_FIELDS; i++)
78     if (field == fields[i].field) return fields[i].string;
79   return "none";
80 }
81
82 std::string CTextureRule::GetField(int field, const std::string &type) const
83 {
84   if (field == TF_Id) return "texture.id";
85   else if (field == TF_Url) return "texture.url";
86   else if (field == TF_CachedUrl) return "texture.cachedurl";
87   else if (field == TF_LastHashCheck) return "texture.lasthashcheck";
88   else if (field == TF_ImageHash) return "texture.imagehash";
89   else if (field == TF_Width) return "sizes.width";
90   else if (field == TF_Height) return "sizes.height";
91   else if (field == TF_UseCount) return "sizes.usecount";
92   else if (field == TF_LastUsed) return "sizes.lastusetime";
93   return "";
94 }
95
96 CDatabaseQueryRule::FIELD_TYPE CTextureRule::GetFieldType(int field) const
97 {
98   for (unsigned int i = 0; i < NUM_FIELDS; i++)
99     if (field == fields[i].field) return fields[i].type;
100   return TEXT_FIELD;
101 }
102
103 std::string CTextureRule::FormatParameter(const std::string &operatorString,
104                                           const std::string &param,
105                                           const CDatabase &db,
106                                           const std::string &strType) const
107 {
108   std::string parameter(param);
109   if (m_field == TF_Url)
110     parameter = CTextureUtils::UnwrapImageURL(param);
111   return CDatabaseQueryRule::FormatParameter(operatorString, parameter, db, strType);
112 }
113
114 void CTextureRule::GetAvailableFields(std::vector<std::string> &fieldList)
115 {
116   // start at 1 to skip TF_None
117   for (unsigned int i = 1; i < NUM_FIELDS; i++)
118     fieldList.push_back(fields[i].string);
119 }
120
121 CStdString CTextureUtils::GetWrappedImageURL(const CStdString &image, const CStdString &type, const CStdString &options)
122 {
123   if (StringUtils::StartsWith(image, "image://"))
124     return image; // already wrapped
125
126   CURL url;
127   url.SetProtocol("image");
128   url.SetUserName(type);
129   url.SetHostName(image);
130   if (!options.empty())
131   {
132     url.SetFileName("transform");
133     url.SetOptions("?" + options);
134   }
135   return url.Get();
136 }
137
138 CStdString CTextureUtils::GetWrappedThumbURL(const CStdString &image)
139 {
140   return GetWrappedImageURL(image, "", "size=thumb");
141 }
142
143 CStdString CTextureUtils::UnwrapImageURL(const CStdString &image)
144 {
145   if (StringUtils::StartsWith(image, "image://"))
146   {
147     CURL url(image);
148     if (url.GetUserName().empty() && url.GetOptions().empty())
149       return url.GetHostName();
150   }
151   return image;
152 }
153
154 CTextureDatabase::CTextureDatabase()
155 {
156 }
157
158 CTextureDatabase::~CTextureDatabase()
159 {
160 }
161
162 bool CTextureDatabase::Open()
163 {
164   return CDatabase::Open();
165 }
166
167 void CTextureDatabase::CreateTables()
168 {
169   CLog::Log(LOGINFO, "create texture table");
170   m_pDS->exec("CREATE TABLE texture (id integer primary key, url text, cachedurl text, imagehash text, lasthashcheck text)");
171
172   CLog::Log(LOGINFO, "create sizes table, index,  and trigger");
173   m_pDS->exec("CREATE TABLE sizes (idtexture integer, size integer, width integer, height integer, usecount integer, lastusetime text)");
174
175   CLog::Log(LOGINFO, "create path table");
176   m_pDS->exec("CREATE TABLE path (id integer primary key, url text, type text, texture text)\n");
177 }
178
179 void CTextureDatabase::CreateAnalytics()
180 {
181   CLog::Log(LOGINFO, "%s creating indices", __FUNCTION__);
182   m_pDS->exec("CREATE INDEX idxTexture ON texture(url)");
183   m_pDS->exec("CREATE INDEX idxSize ON sizes(idtexture, size)");
184   m_pDS->exec("CREATE INDEX idxSize2 ON sizes(idtexture, width, height)");
185   // TODO: Should the path index be a covering index? (we need only retrieve texture)
186   m_pDS->exec("CREATE INDEX idxPath ON path(url, type)");
187
188   CLog::Log(LOGINFO, "%s creating triggers", __FUNCTION__);
189   m_pDS->exec("CREATE TRIGGER textureDelete AFTER delete ON texture FOR EACH ROW BEGIN delete from sizes where sizes.idtexture=old.id; END");
190 }
191
192 void CTextureDatabase::UpdateTables(int version)
193 {
194   if (version < 7)
195   { // update all old thumb://foo urls to image://foo?size=thumb
196     m_pDS->query("select id,texture from path where texture like 'thumb://%'");
197     while (!m_pDS->eof())
198     {
199       unsigned int id = m_pDS->fv(0).get_asInt();
200       CURL url(m_pDS->fv(1).get_asString());
201       m_pDS2->exec(PrepareSQL("update path set texture='image://%s?size=thumb' where id=%u", url.GetHostName().c_str(), id));
202       m_pDS->next();
203     }
204     m_pDS->query("select id, url from texture where url like 'thumb://%'");
205     while (!m_pDS->eof())
206     {
207       unsigned int id = m_pDS->fv(0).get_asInt();
208       CURL url(m_pDS->fv(1).get_asString());
209       m_pDS2->exec(PrepareSQL("update texture set url='image://%s?size=thumb', urlhash=0 where id=%u", url.GetHostName().c_str(), id));
210       m_pDS->next();
211     }
212     m_pDS->close();
213   }
214   if (version < 8)
215   { // get rid of old cached thumbs as they were previously set to the cached thumb name instead of the source thumb
216     m_pDS->exec("delete from path");
217   }
218   if (version < 9)
219   { // get rid of the old path table and add the type column
220     m_pDS->exec("DROP TABLE IF EXISTS path");
221     m_pDS->exec("CREATE TABLE path (id integer primary key, urlhash integer, url text, type text, texture text)\n");
222   }
223   if (version < 10)
224   { // get rid of urlhash in both tables...
225     m_pDS->exec("DROP TABLE IF EXISTS path");
226     m_pDS->exec("CREATE TABLE path (id integer primary key, url text, type text, texture text)\n");
227
228     m_pDS->exec("CREATE TEMPORARY TABLE texture_backup(id,url,cachedurl,usecount,lastusetime,imagehash,lasthashcheck)");
229     m_pDS->exec("INSERT INTO texture_backup SELECT id,url,cachedurl,usecount,lastusetime,imagehash,lasthashcheck FROM texture");
230     m_pDS->exec("DROP TABLE texture");
231     m_pDS->exec("CREATE TABLE texture (id integer primary key, url text, cachedurl text, usecount integer, lastusetime text, imagehash text, lasthashcheck text)");
232     m_pDS->exec("INSERT INTO texture SELECT * FROM texture_backup");
233     m_pDS->exec("DROP TABLE texture_backup");
234   }
235   if (version < 11)
236   { // get rid of cached URLs that don't have the correct extension
237     m_pDS->exec("DELETE FROM texture WHERE SUBSTR(cachedUrl,-4,4) NOT IN ('.jpg', '.png')");
238   }
239   if (version < 12)
240   { // create new sizes table and move usecount info to it.
241     m_pDS->exec("DROP TABLE IF EXISTS texture");
242     m_pDS->exec("CREATE TABLE texture (id integer primary key, url text, cachedurl text, imagehash text, lasthashcheck text)");
243     m_pDS->exec("CREATE TABLE sizes (idtexture integer, size integer, width integer, height integer, usecount integer, lastusetime text)");
244   }
245 }
246
247 bool CTextureDatabase::IncrementUseCount(const CTextureDetails &details)
248 {
249   CStdString sql = PrepareSQL("UPDATE sizes SET usecount=usecount+1, lastusetime=CURRENT_TIMESTAMP WHERE idtexture=%u AND width=%u AND height=%u", details.id, details.width, details.height);
250   return ExecuteQuery(sql);
251 }
252
253 bool CTextureDatabase::GetCachedTexture(const CStdString &url, CTextureDetails &details)
254 {
255   try
256   {
257     if (NULL == m_pDB.get()) return false;
258     if (NULL == m_pDS.get()) return false;
259
260     CStdString sql = PrepareSQL("SELECT id, cachedurl, lasthashcheck, imagehash, width, height FROM texture JOIN sizes ON (texture.id=sizes.idtexture AND sizes.size=1) WHERE url='%s'", url.c_str());
261     m_pDS->query(sql.c_str());
262     if (!m_pDS->eof())
263     { // have some information
264       details.id = m_pDS->fv(0).get_asInt();
265       details.file  = m_pDS->fv(1).get_asString();
266       CDateTime lastCheck;
267       lastCheck.SetFromDBDateTime(m_pDS->fv(2).get_asString());
268       if (lastCheck.IsValid() && lastCheck + CDateTimeSpan(1,0,0,0) < CDateTime::GetCurrentDateTime())
269         details.hash = m_pDS->fv(3).get_asString();
270       details.width = m_pDS->fv(4).get_asInt();
271       details.height = m_pDS->fv(5).get_asInt();
272       m_pDS->close();
273       return true;
274     }
275     m_pDS->close();
276   }
277   catch (...)
278   {
279     CLog::Log(LOGERROR, "%s, failed on url '%s'", __FUNCTION__, url.c_str());
280   }
281   return false;
282 }
283
284 bool CTextureDatabase::GetTextures(CVariant &items, const Filter &filter)
285 {
286   try
287   {
288     if (NULL == m_pDB.get()) return false;
289     if (NULL == m_pDS.get()) return false;
290
291     std::string sql = "SELECT %s FROM texture JOIN sizes ON (texture.id=sizes.idtexture AND sizes.size=1)";
292     std::string sqlFilter;
293     if (!CDatabase::BuildSQL("", filter, sqlFilter))
294       return false;
295
296     sql = PrepareSQL(sql, !filter.fields.empty() ? filter.fields.c_str() : "*") + sqlFilter;
297     if (!m_pDS->query(sql.c_str()))
298       return false;
299
300     while (!m_pDS->eof())
301     {
302       CVariant texture;
303       texture["textureid"] = m_pDS->fv(0).get_asInt();
304       texture["url"] = m_pDS->fv(1).get_asString();
305       texture["cachedurl"] = m_pDS->fv(2).get_asString();
306       texture["imagehash"] = m_pDS->fv(3).get_asString();
307       texture["lasthashcheck"] = m_pDS->fv(4).get_asString();
308       CVariant size(CVariant::VariantTypeObject);
309       // 5 is sizes.idtexture
310       size["size"]  = m_pDS->fv(6).get_asInt();
311       size["width"] = m_pDS->fv(7).get_asInt();
312       size["height"] = m_pDS->fv(8).get_asInt();
313       size["usecount"] = m_pDS->fv(9).get_asInt();
314       size["lastused"] = m_pDS->fv(10).get_asString();
315       texture["sizes"] = CVariant(CVariant::VariantTypeArray);
316       texture["sizes"].push_back(size);
317       items.push_back(texture);
318       m_pDS->next();
319     }
320     m_pDS->close();
321     return true;
322   }
323   catch (...)
324   {
325     CLog::Log(LOGERROR, "%s, failed", __FUNCTION__);
326   }
327   return false;
328 }
329
330 bool CTextureDatabase::SetCachedTextureValid(const CStdString &url, bool updateable)
331 {
332   CStdString date = updateable ? CDateTime::GetCurrentDateTime().GetAsDBDateTime() : "";
333   CStdString sql = PrepareSQL("UPDATE texture SET lasthashcheck='%s' WHERE url='%s'", date.c_str(), url.c_str());
334   return ExecuteQuery(sql);
335 }
336
337 bool CTextureDatabase::AddCachedTexture(const CStdString &url, const CTextureDetails &details)
338 {
339   try
340   {
341     if (NULL == m_pDB.get()) return false;
342     if (NULL == m_pDS.get()) return false;
343
344     CStdString sql = PrepareSQL("DELETE FROM texture WHERE url='%s'", url.c_str());
345     m_pDS->exec(sql.c_str());
346
347     CStdString date = details.updateable ? CDateTime::GetCurrentDateTime().GetAsDBDateTime() : "";
348     sql = PrepareSQL("INSERT INTO texture (id, url, cachedurl, imagehash, lasthashcheck) VALUES(NULL, '%s', '%s', '%s', '%s')", url.c_str(), details.file.c_str(), details.hash.c_str(), date.c_str());
349     m_pDS->exec(sql.c_str());
350     int textureID = (int)m_pDS->lastinsertid();
351
352     // set the size information
353     sql = PrepareSQL("INSERT INTO sizes (idtexture, size, usecount, lastusetime, width, height) VALUES(%u, 1, 1, CURRENT_TIMESTAMP, %u, %u)", textureID, details.width, details.height);
354     m_pDS->exec(sql.c_str());
355   }
356   catch (...)
357   {
358     CLog::Log(LOGERROR, "%s failed on url '%s'", __FUNCTION__, url.c_str());
359   }
360   return true;
361 }
362
363 bool CTextureDatabase::ClearCachedTexture(const CStdString &url, CStdString &cacheFile)
364 {
365   std::string id = GetSingleValue(PrepareSQL("select id from texture where url='%s'", url.c_str()));
366   return !id.empty() ? ClearCachedTexture(strtol(id.c_str(), NULL, 10), cacheFile) : false;
367 }
368
369 bool CTextureDatabase::ClearCachedTexture(int id, CStdString &cacheFile)
370 {
371   try
372   {
373     if (NULL == m_pDB.get()) return false;
374     if (NULL == m_pDS.get()) return false;
375
376     CStdString sql = PrepareSQL("select cachedurl from texture where id=%u", id);
377     m_pDS->query(sql.c_str());
378
379     if (!m_pDS->eof())
380     { // have some information
381       cacheFile = m_pDS->fv(0).get_asString();
382       m_pDS->close();
383       // remove it
384       sql = PrepareSQL("delete from texture where id=%u", id);
385       m_pDS->exec(sql.c_str());
386       return true;
387     }
388     m_pDS->close();
389   }
390   catch (...)
391   {
392     CLog::Log(LOGERROR, "%s, failed on texture id %u", __FUNCTION__, id);
393   }
394   return false;
395 }
396
397 bool CTextureDatabase::InvalidateCachedTexture(const CStdString &url)
398 {
399   CStdString date = (CDateTime::GetCurrentDateTime() - CDateTimeSpan(2, 0, 0, 0)).GetAsDBDateTime();
400   CStdString sql = PrepareSQL("UPDATE texture SET lasthashcheck='%s' WHERE url='%s'", date.c_str(), url.c_str());
401   return ExecuteQuery(sql);
402 }
403
404 CStdString CTextureDatabase::GetTextureForPath(const CStdString &url, const CStdString &type)
405 {
406   try
407   {
408     if (NULL == m_pDB.get()) return "";
409     if (NULL == m_pDS.get()) return "";
410
411     if (url.empty())
412       return "";
413
414     CStdString sql = PrepareSQL("select texture from path where url='%s' and type='%s'", url.c_str(), type.c_str());
415     m_pDS->query(sql.c_str());
416
417     if (!m_pDS->eof())
418     { // have some information
419       CStdString texture = m_pDS->fv(0).get_asString();
420       m_pDS->close();
421       return texture;
422     }
423     m_pDS->close();
424   }
425   catch (...)
426   {
427     CLog::Log(LOGERROR, "%s, failed on url '%s'", __FUNCTION__, url.c_str());
428   }
429   return "";
430 }
431
432 void CTextureDatabase::SetTextureForPath(const CStdString &url, const CStdString &type, const CStdString &texture)
433 {
434   try
435   {
436     if (NULL == m_pDB.get()) return;
437     if (NULL == m_pDS.get()) return;
438
439     if (url.empty())
440       return;
441
442     CStdString sql = PrepareSQL("select id from path where url='%s' and type='%s'", url.c_str(), type.c_str());
443     m_pDS->query(sql.c_str());
444     if (!m_pDS->eof())
445     { // update
446       int pathID = m_pDS->fv(0).get_asInt();
447       m_pDS->close();
448       sql = PrepareSQL("update path set texture='%s' where id=%u", texture.c_str(), pathID);
449       m_pDS->exec(sql.c_str());
450     }
451     else
452     { // add the texture
453       m_pDS->close();
454       sql = PrepareSQL("insert into path (id, url, type, texture) values(NULL, '%s', '%s', '%s')", url.c_str(), type.c_str(), texture.c_str());
455       m_pDS->exec(sql.c_str());
456     }
457   }
458   catch (...)
459   {
460     CLog::Log(LOGERROR, "%s failed on url '%s'", __FUNCTION__, url.c_str());
461   }
462   return;
463 }
464
465 void CTextureDatabase::ClearTextureForPath(const CStdString &url, const CStdString &type)
466 {
467   try
468   {
469     if (NULL == m_pDB.get()) return;
470     if (NULL == m_pDS.get()) return;
471
472     CStdString sql = PrepareSQL("DELETE FROM path WHERE url='%s' and type='%s'", url.c_str(), type.c_str());
473     m_pDS->exec(sql.c_str());
474   }
475   catch (...)
476   {
477     CLog::Log(LOGERROR, "%s failed on url '%s'", __FUNCTION__, url.c_str());
478   }
479   return;
480 }
481
482 CDatabaseQueryRule *CTextureDatabase::CreateRule() const
483 {
484   return new CTextureRule();
485 }
486
487 CDatabaseQueryRuleCombination *CTextureDatabase::CreateCombination() const
488 {
489   return new CDatabaseQueryRuleCombination();
490 }