2 * Copyright (C) 2005-2013 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 "TextureDatabase.h"
22 #include "utils/log.h"
23 #include "XBDateTime.h"
24 #include "dbwrappers/dataset.h"
26 #include "utils/StringUtils.h"
27 #include "utils/Variant.h"
28 #include "utils/DatabaseUtils.h"
49 CDatabaseQueryRule::FIELD_TYPE type
;
54 static const translateField fields
[] = {
55 { "none", TF_None
, CDatabaseQueryRule::TEXT_FIELD
},
56 { "textureid", TF_Id
, CDatabaseQueryRule::REAL_FIELD
},
57 { "url", TF_Url
, CDatabaseQueryRule::TEXT_FIELD
},
58 { "cachedurl", TF_CachedUrl
, CDatabaseQueryRule::TEXT_FIELD
},
59 { "lasthashcheck", TF_LastHashCheck
, CDatabaseQueryRule::TEXT_FIELD
},
60 { "imagehash", TF_ImageHash
, CDatabaseQueryRule::TEXT_FIELD
},
61 { "width", TF_Width
, CDatabaseQueryRule::REAL_FIELD
},
62 { "height", TF_Height
, CDatabaseQueryRule::REAL_FIELD
},
63 { "usecount", TF_UseCount
, CDatabaseQueryRule::REAL_FIELD
},
64 { "lastused", TF_LastUsed
, CDatabaseQueryRule::TEXT_FIELD
}
67 static const size_t NUM_FIELDS
= sizeof(fields
) / sizeof(translateField
);
69 int CTextureRule::TranslateField(const char *field
) const
71 for (unsigned int i
= 0; i
< NUM_FIELDS
; i
++)
72 if (StringUtils::EqualsNoCase(field
, fields
[i
].string
)) return fields
[i
].field
;
76 std::string
CTextureRule::TranslateField(int field
) const
78 for (unsigned int i
= 0; i
< NUM_FIELDS
; i
++)
79 if (field
== fields
[i
].field
) return fields
[i
].string
;
83 std::string
CTextureRule::GetField(int field
, const std::string
&type
) const
85 if (field
== TF_Id
) return "texture.id";
86 else if (field
== TF_Url
) return "texture.url";
87 else if (field
== TF_CachedUrl
) return "texture.cachedurl";
88 else if (field
== TF_LastHashCheck
) return "texture.lasthashcheck";
89 else if (field
== TF_ImageHash
) return "texture.imagehash";
90 else if (field
== TF_Width
) return "sizes.width";
91 else if (field
== TF_Height
) return "sizes.height";
92 else if (field
== TF_UseCount
) return "sizes.usecount";
93 else if (field
== TF_LastUsed
) return "sizes.lastusetime";
97 CDatabaseQueryRule::FIELD_TYPE
CTextureRule::GetFieldType(int field
) const
99 for (unsigned int i
= 0; i
< NUM_FIELDS
; i
++)
100 if (field
== fields
[i
].field
) return fields
[i
].type
;
104 std::string
CTextureRule::FormatParameter(const std::string
&operatorString
,
105 const std::string
¶m
,
107 const std::string
&strType
) const
109 std::string
parameter(param
);
110 if (m_field
== TF_Url
)
111 parameter
= CTextureUtils::UnwrapImageURL(param
);
112 return CDatabaseQueryRule::FormatParameter(operatorString
, parameter
, db
, strType
);
115 void CTextureRule::GetAvailableFields(std::vector
<std::string
> &fieldList
)
117 // start at 1 to skip TF_None
118 for (unsigned int i
= 1; i
< NUM_FIELDS
; i
++)
119 fieldList
.push_back(fields
[i
].string
);
122 std::string
CTextureUtils::GetWrappedImageURL(const std::string
&image
, const std::string
&type
, const std::string
&options
)
124 if (StringUtils::StartsWith(image
, "image://"))
125 return image
; // already wrapped
128 url
.SetProtocol("image");
129 url
.SetUserName(type
);
130 url
.SetHostName(image
);
131 if (!options
.empty())
133 url
.SetFileName("transform");
134 url
.SetOptions("?" + options
);
139 std::string
CTextureUtils::GetWrappedThumbURL(const std::string
&image
)
141 return GetWrappedImageURL(image
, "", "size=thumb");
144 std::string
CTextureUtils::UnwrapImageURL(const std::string
&image
)
146 if (StringUtils::StartsWith(image
, "image://"))
149 if (url
.GetUserName().empty() && url
.GetOptions().empty())
150 return url
.GetHostName();
155 CTextureDatabase::CTextureDatabase()
159 CTextureDatabase::~CTextureDatabase()
163 bool CTextureDatabase::Open()
165 return CDatabase::Open();
168 void CTextureDatabase::CreateTables()
170 CLog::Log(LOGINFO
, "create texture table");
171 m_pDS
->exec("CREATE TABLE texture (id integer primary key, url text, cachedurl text, imagehash text, lasthashcheck text)");
173 CLog::Log(LOGINFO
, "create sizes table, index, and trigger");
174 m_pDS
->exec("CREATE TABLE sizes (idtexture integer, size integer, width integer, height integer, usecount integer, lastusetime text)");
176 CLog::Log(LOGINFO
, "create path table");
177 m_pDS
->exec("CREATE TABLE path (id integer primary key, url text, type text, texture text)\n");
180 void CTextureDatabase::CreateAnalytics()
182 CLog::Log(LOGINFO
, "%s creating indices", __FUNCTION__
);
183 m_pDS
->exec("CREATE INDEX idxTexture ON texture(url)");
184 m_pDS
->exec("CREATE INDEX idxSize ON sizes(idtexture, size)");
185 m_pDS
->exec("CREATE INDEX idxSize2 ON sizes(idtexture, width, height)");
186 //! @todo Should the path index be a covering index? (we need only retrieve texture)
187 m_pDS
->exec("CREATE INDEX idxPath ON path(url, type)");
189 CLog::Log(LOGINFO
, "%s creating triggers", __FUNCTION__
);
190 m_pDS
->exec("CREATE TRIGGER textureDelete AFTER delete ON texture FOR EACH ROW BEGIN delete from sizes where sizes.idtexture=old.id; END");
193 void CTextureDatabase::UpdateTables(int version
)
196 { // update all old thumb://foo urls to image://foo?size=thumb
197 m_pDS
->query("select id,texture from path where texture like 'thumb://%'");
198 while (!m_pDS
->eof())
200 unsigned int id
= m_pDS
->fv(0).get_asInt();
201 CURL
url(m_pDS
->fv(1).get_asString());
202 m_pDS2
->exec(PrepareSQL("update path set texture='image://%s?size=thumb' where id=%u", url
.GetHostName().c_str(), id
));
205 m_pDS
->query("select id, url from texture where url like 'thumb://%'");
206 while (!m_pDS
->eof())
208 unsigned int id
= m_pDS
->fv(0).get_asInt();
209 CURL
url(m_pDS
->fv(1).get_asString());
210 m_pDS2
->exec(PrepareSQL("update texture set url='image://%s?size=thumb', urlhash=0 where id=%u", url
.GetHostName().c_str(), id
));
216 { // get rid of old cached thumbs as they were previously set to the cached thumb name instead of the source thumb
217 m_pDS
->exec("delete from path");
220 { // get rid of the old path table and add the type column
221 m_pDS
->exec("DROP TABLE IF EXISTS path");
222 m_pDS
->exec("CREATE TABLE path (id integer primary key, urlhash integer, url text, type text, texture text)\n");
225 { // get rid of urlhash in both tables...
226 m_pDS
->exec("DROP TABLE IF EXISTS path");
227 m_pDS
->exec("CREATE TABLE path (id integer primary key, url text, type text, texture text)\n");
229 m_pDS
->exec("CREATE TEMPORARY TABLE texture_backup(id,url,cachedurl,usecount,lastusetime,imagehash,lasthashcheck)");
230 m_pDS
->exec("INSERT INTO texture_backup SELECT id,url,cachedurl,usecount,lastusetime,imagehash,lasthashcheck FROM texture");
231 m_pDS
->exec("DROP TABLE texture");
232 m_pDS
->exec("CREATE TABLE texture (id integer primary key, url text, cachedurl text, usecount integer, lastusetime text, imagehash text, lasthashcheck text)");
233 m_pDS
->exec("INSERT INTO texture SELECT * FROM texture_backup");
234 m_pDS
->exec("DROP TABLE texture_backup");
237 { // get rid of cached URLs that don't have the correct extension
238 m_pDS
->exec("DELETE FROM texture WHERE SUBSTR(cachedUrl,-4,4) NOT IN ('.jpg', '.png')");
241 { // create new sizes table and move usecount info to it.
242 m_pDS
->exec("DROP TABLE IF EXISTS texture");
243 m_pDS
->exec("CREATE TABLE texture (id integer primary key, url text, cachedurl text, imagehash text, lasthashcheck text)");
244 m_pDS
->exec("CREATE TABLE sizes (idtexture integer, size integer, width integer, height integer, usecount integer, lastusetime text)");
248 bool CTextureDatabase::IncrementUseCount(const CTextureDetails
&details
)
250 std::string 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
);
251 return ExecuteQuery(sql
);
254 bool CTextureDatabase::GetCachedTexture(const std::string
&url
, CTextureDetails
&details
)
258 if (NULL
== m_pDB
.get()) return false;
259 if (NULL
== m_pDS
.get()) return false;
261 std::string 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());
264 { // have some information
265 details
.id
= m_pDS
->fv(0).get_asInt();
266 details
.file
= m_pDS
->fv(1).get_asString();
268 lastCheck
.SetFromDBDateTime(m_pDS
->fv(2).get_asString());
269 if (lastCheck
.IsValid() && lastCheck
+ CDateTimeSpan(1,0,0,0) < CDateTime::GetCurrentDateTime())
270 details
.hash
= m_pDS
->fv(3).get_asString();
271 details
.width
= m_pDS
->fv(4).get_asInt();
272 details
.height
= m_pDS
->fv(5).get_asInt();
280 CLog::Log(LOGERROR
, "%s, failed on url '%s'", __FUNCTION__
, url
.c_str());
285 bool CTextureDatabase::GetTextures(CVariant
&items
, const Filter
&filter
)
289 if (NULL
== m_pDB
.get()) return false;
290 if (NULL
== m_pDS
.get()) return false;
292 std::string sql
= "SELECT %s FROM texture JOIN sizes ON (texture.id=sizes.idtexture AND sizes.size=1)";
293 std::string sqlFilter
;
294 if (!CDatabase::BuildSQL("", filter
, sqlFilter
))
297 sql
= PrepareSQL(sql
, !filter
.fields
.empty() ? filter
.fields
.c_str() : "*") + sqlFilter
;
298 if (!m_pDS
->query(sql
))
301 while (!m_pDS
->eof())
304 texture
["textureid"] = m_pDS
->fv(0).get_asInt();
305 texture
["url"] = m_pDS
->fv(1).get_asString();
306 texture
["cachedurl"] = m_pDS
->fv(2).get_asString();
307 texture
["imagehash"] = m_pDS
->fv(3).get_asString();
308 texture
["lasthashcheck"] = m_pDS
->fv(4).get_asString();
309 CVariant
size(CVariant::VariantTypeObject
);
310 // 5 is sizes.idtexture
311 size
["size"] = m_pDS
->fv(6).get_asInt();
312 size
["width"] = m_pDS
->fv(7).get_asInt();
313 size
["height"] = m_pDS
->fv(8).get_asInt();
314 size
["usecount"] = m_pDS
->fv(9).get_asInt();
315 size
["lastused"] = m_pDS
->fv(10).get_asString();
316 texture
["sizes"] = CVariant(CVariant::VariantTypeArray
);
317 texture
["sizes"].push_back(size
);
318 items
.push_back(texture
);
326 CLog::Log(LOGERROR
, "%s, failed", __FUNCTION__
);
331 bool CTextureDatabase::SetCachedTextureValid(const std::string
&url
, bool updateable
)
333 std::string date
= updateable
? CDateTime::GetCurrentDateTime().GetAsDBDateTime() : "";
334 std::string sql
= PrepareSQL("UPDATE texture SET lasthashcheck='%s' WHERE url='%s'", date
.c_str(), url
.c_str());
335 return ExecuteQuery(sql
);
338 bool CTextureDatabase::AddCachedTexture(const std::string
&url
, const CTextureDetails
&details
)
342 if (NULL
== m_pDB
.get()) return false;
343 if (NULL
== m_pDS
.get()) return false;
345 std::string sql
= PrepareSQL("DELETE FROM texture WHERE url='%s'", url
.c_str());
348 std::string date
= details
.updateable
? CDateTime::GetCurrentDateTime().GetAsDBDateTime() : "";
349 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());
351 int textureID
= (int)m_pDS
->lastinsertid();
353 // set the size information
354 sql
= PrepareSQL("INSERT INTO sizes (idtexture, size, usecount, lastusetime, width, height) VALUES(%u, 1, 1, CURRENT_TIMESTAMP, %u, %u)", textureID
, details
.width
, details
.height
);
359 CLog::Log(LOGERROR
, "%s failed on url '%s'", __FUNCTION__
, url
.c_str());
364 bool CTextureDatabase::ClearCachedTexture(const std::string
&url
, std::string
&cacheFile
)
366 std::string id
= GetSingleValue(PrepareSQL("select id from texture where url='%s'", url
.c_str()));
367 return !id
.empty() ? ClearCachedTexture(strtol(id
.c_str(), NULL
, 10), cacheFile
) : false;
370 bool CTextureDatabase::ClearCachedTexture(int id
, std::string
&cacheFile
)
374 if (NULL
== m_pDB
.get()) return false;
375 if (NULL
== m_pDS
.get()) return false;
377 std::string sql
= PrepareSQL("select cachedurl from texture where id=%u", id
);
381 { // have some information
382 cacheFile
= m_pDS
->fv(0).get_asString();
385 sql
= PrepareSQL("delete from texture where id=%u", id
);
393 CLog::Log(LOGERROR
, "%s, failed on texture id %u", __FUNCTION__
, id
);
398 bool CTextureDatabase::InvalidateCachedTexture(const std::string
&url
)
400 std::string date
= (CDateTime::GetCurrentDateTime() - CDateTimeSpan(2, 0, 0, 0)).GetAsDBDateTime();
401 std::string sql
= PrepareSQL("UPDATE texture SET lasthashcheck='%s' WHERE url='%s'", date
.c_str(), url
.c_str());
402 return ExecuteQuery(sql
);
405 std::string
CTextureDatabase::GetTextureForPath(const std::string
&url
, const std::string
&type
)
409 if (NULL
== m_pDB
.get()) return "";
410 if (NULL
== m_pDS
.get()) return "";
415 std::string sql
= PrepareSQL("select texture from path where url='%s' and type='%s'", url
.c_str(), type
.c_str());
419 { // have some information
420 std::string texture
= m_pDS
->fv(0).get_asString();
428 CLog::Log(LOGERROR
, "%s, failed on url '%s'", __FUNCTION__
, url
.c_str());
433 void CTextureDatabase::SetTextureForPath(const std::string
&url
, const std::string
&type
, const std::string
&texture
)
437 if (NULL
== m_pDB
.get()) return;
438 if (NULL
== m_pDS
.get()) return;
443 std::string sql
= PrepareSQL("select id from path where url='%s' and type='%s'", url
.c_str(), type
.c_str());
447 int pathID
= m_pDS
->fv(0).get_asInt();
449 sql
= PrepareSQL("update path set texture='%s' where id=%u", texture
.c_str(), pathID
);
455 sql
= PrepareSQL("insert into path (id, url, type, texture) values(NULL, '%s', '%s', '%s')", url
.c_str(), type
.c_str(), texture
.c_str());
461 CLog::Log(LOGERROR
, "%s failed on url '%s'", __FUNCTION__
, url
.c_str());
466 void CTextureDatabase::ClearTextureForPath(const std::string
&url
, const std::string
&type
)
470 if (NULL
== m_pDB
.get()) return;
471 if (NULL
== m_pDS
.get()) return;
473 std::string sql
= PrepareSQL("DELETE FROM path WHERE url='%s' and type='%s'", url
.c_str(), type
.c_str());
478 CLog::Log(LOGERROR
, "%s failed on url '%s'", __FUNCTION__
, url
.c_str());
483 CDatabaseQueryRule
*CTextureDatabase::CreateRule() const
485 return new CTextureRule();
488 CDatabaseQueryRuleCombination
*CTextureDatabase::CreateCombination() const
490 return new CDatabaseQueryRuleCombination();