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 "AddonDatabase.h"
22 #include "addons/AddonManager.h"
23 #include "utils/log.h"
24 #include "utils/Variant.h"
25 #include "utils/StringUtils.h"
26 #include "XBDateTime.h"
27 #include "addons/Service.h"
28 #include "dbwrappers/dataset.h"
29 #include "pvr/PVRManager.h"
31 using namespace ADDON;
34 CAddonDatabase::CAddonDatabase()
38 CAddonDatabase::~CAddonDatabase()
42 bool CAddonDatabase::Open()
44 return CDatabase::Open();
47 bool CAddonDatabase::CreateTables()
51 CDatabase::CreateTables();
53 CLog::Log(LOGINFO, "create addon table");
54 m_pDS->exec("CREATE TABLE addon (id integer primary key, type text,"
55 "name text, summary text, description text, stars integer,"
56 "path text, addonID text, icon text, version text, "
57 "changelog text, fanart text, author text, disclaimer text,"
58 "minversion text)\n");
60 CLog::Log(LOGINFO, "create addon index");
61 m_pDS->exec("CREATE INDEX idxAddon ON addon(addonID)");
63 CLog::Log(LOGINFO, "create addonextra table");
64 m_pDS->exec("CREATE TABLE addonextra (id integer, key text, value text)\n");
66 CLog::Log(LOGINFO, "create addonextra index");
67 m_pDS->exec("CREATE INDEX idxAddonExtra ON addonextra(id)");
69 CLog::Log(LOGINFO, "create dependencies table");
70 m_pDS->exec("CREATE TABLE dependencies (id integer, addon text, version text, optional boolean)\n");
71 m_pDS->exec("CREATE INDEX idxDependencies ON dependencies(id)");
73 CLog::Log(LOGINFO, "create repo table");
74 m_pDS->exec("CREATE TABLE repo (id integer primary key, addonID text,"
75 "checksum text, lastcheck text)\n");
77 CLog::Log(LOGINFO, "create addonlinkrepo table");
78 m_pDS->exec("CREATE TABLE addonlinkrepo (idRepo integer, idAddon integer)\n");
79 m_pDS->exec("CREATE UNIQUE INDEX ix_addonlinkrepo_1 ON addonlinkrepo ( idAddon, idRepo )\n");
80 m_pDS->exec("CREATE UNIQUE INDEX ix_addonlinkrepo_2 ON addonlinkrepo ( idRepo, idAddon )\n");
82 CLog::Log(LOGINFO, "create disabled table");
83 m_pDS->exec("CREATE TABLE disabled (id integer primary key, addonID text)\n");
84 m_pDS->exec("CREATE UNIQUE INDEX idxDisabled ON disabled(addonID)");
86 CLog::Log(LOGINFO, "create broken table");
87 m_pDS->exec("CREATE TABLE broken (id integer primary key, addonID text, reason text)\n");
88 m_pDS->exec("CREATE UNIQUE INDEX idxBroken ON broken(addonID)");
90 CLog::Log(LOGINFO, "create blacklist table");
91 m_pDS->exec("CREATE TABLE blacklist (id integer primary key, addonID text, version text)\n");
92 m_pDS->exec("CREATE UNIQUE INDEX idxBlack ON blacklist(addonID)");
96 CLog::Log(LOGERROR, "%s unable to create tables", __FUNCTION__);
103 bool CAddonDatabase::UpdateOldVersion(int version)
107 m_pDS->exec("CREATE TABLE dependencies (id integer, addon text, version text, optional boolean)\n");
108 m_pDS->exec("CREATE INDEX idxDependencies ON dependencies(id)");
112 m_pDS->exec("ALTER TABLE addon add minversion text");
116 m_pDS->exec("CREATE TABLE blacklist (id integer primary key, addonID text, version text)\n");
117 m_pDS->exec("CREATE UNIQUE INDEX idxBlack ON blacklist(addonID)");
122 int CAddonDatabase::AddAddon(const AddonPtr& addon,
127 if (NULL == m_pDB.get()) return -1;
128 if (NULL == m_pDS.get()) return -1;
130 bool bDisablePVRAddon = addon->Type() == ADDON_PVRDLL && !HasAddon(addon->ID());
132 CStdString sql = PrepareSQL("insert into addon (id, type, name, summary,"
133 "description, stars, path, icon, changelog, "
134 "fanart, addonID, version, author, disclaimer, minversion)"
135 " values(NULL, '%s', '%s', '%s', '%s', %i,"
136 "'%s', '%s', '%s', '%s', '%s','%s','%s','%s','%s')",
137 TranslateType(addon->Type(),false).c_str(),
138 addon->Name().c_str(), addon->Summary().c_str(),
139 addon->Description().c_str(),addon->Stars(),
140 addon->Path().c_str(), addon->Props().icon.c_str(),
141 addon->ChangeLog().c_str(),addon->FanArt().c_str(),
142 addon->ID().c_str(), addon->Version().c_str(),
143 addon->Author().c_str(),addon->Disclaimer().c_str(),
144 addon->MinVersion().c_str());
145 m_pDS->exec(sql.c_str());
146 int idAddon = (int)m_pDS->lastinsertid();
148 sql = PrepareSQL("insert into addonlinkrepo (idRepo, idAddon) values (%i,%i)",idRepo,idAddon);
149 m_pDS->exec(sql.c_str());
151 const InfoMap &info = addon->ExtraInfo();
152 for (InfoMap::const_iterator i = info.begin(); i != info.end(); ++i)
154 sql = PrepareSQL("insert into addonextra(id, key, value) values (%i, '%s', '%s')", idAddon, i->first.c_str(), i->second.c_str());
155 m_pDS->exec(sql.c_str());
157 const ADDONDEPS &deps = addon->GetDeps();
158 for (ADDONDEPS::const_iterator i = deps.begin(); i != deps.end(); ++i)
160 sql = PrepareSQL("insert into dependencies(id, addon, version, optional) values (%i, '%s', '%s', %i)", idAddon, i->first.c_str(), i->second.first.c_str(), i->second.second ? 1 : 0);
161 m_pDS->exec(sql.c_str());
163 // these need to be configured
164 if (bDisablePVRAddon)
165 DisableAddon(addon->ID(), true);
170 CLog::Log(LOGERROR, "%s failed on addon '%s'", __FUNCTION__, addon->Name().c_str());
175 bool CAddonDatabase::GetAddon(const CStdString& id, AddonPtr& addon)
179 if (NULL == m_pDB.get()) return false;
180 if (NULL == m_pDS2.get()) return false;
182 // there may be multiple addons with this id (eg from different repositories) in the database,
183 // so we want to retrieve the latest version. Order by version won't work as the database
184 // won't know that 1.10 > 1.2, so grab them all and order outside
185 CStdString sql = PrepareSQL("select id,version from addon where addonID='%s'",id.c_str());
186 m_pDS2->query(sql.c_str());
191 AddonVersion maxversion("0.0.0");
193 while (!m_pDS2->eof())
195 AddonVersion version(m_pDS2->fv(1).get_asString());
196 if (version > maxversion)
198 maxid = m_pDS2->fv(0).get_asInt();
199 maxversion = version;
203 return GetAddon(maxid,addon);
207 CLog::Log(LOGERROR, "%s failed on addon %s", __FUNCTION__, id.c_str());
213 bool CAddonDatabase::GetRepoForAddon(const CStdString& addonID, CStdString& repo)
217 if (NULL == m_pDB.get()) return false;
218 if (NULL == m_pDS2.get()) return false;
220 CStdString sql = PrepareSQL("select repo.addonID from repo join addonlinkrepo on repo.id=addonlinkrepo.idRepo join addon on addonlinkrepo.idAddon=addon.id where addon.addonID like '%s'", addonID.c_str());
221 m_pDS2->query(sql.c_str());
224 repo = m_pDS2->fv(0).get_asString();
231 CLog::Log(LOGERROR, "%s failed for addon %s", __FUNCTION__, addonID.c_str());
236 bool CAddonDatabase::GetAddon(int id, AddonPtr& addon)
240 if (NULL == m_pDB.get()) return false;
241 if (NULL == m_pDS2.get()) return false;
243 CStdString sql = PrepareSQL("select * from addon where id=%i",id);
244 m_pDS2->query(sql.c_str());
247 AddonProps props(m_pDS2->fv("addonID" ).get_asString(),
248 TranslateType(m_pDS2->fv("type").get_asString()),
249 m_pDS2->fv("version").get_asString(),
250 m_pDS2->fv("minversion").get_asString());
251 props.name = m_pDS2->fv("name").get_asString();
252 props.summary = m_pDS2->fv("summary").get_asString();
253 props.description = m_pDS2->fv("description").get_asString();
254 props.changelog = m_pDS2->fv("changelog").get_asString();
255 props.path = m_pDS2->fv("path").get_asString();
256 props.icon = m_pDS2->fv("icon").get_asString();
257 props.fanart = m_pDS2->fv("fanart").get_asString();
258 props.author = m_pDS2->fv("author").get_asString();
259 props.disclaimer = m_pDS2->fv("disclaimer").get_asString();
260 sql = PrepareSQL("select reason from broken where addonID='%s'",props.id.c_str());
261 m_pDS2->query(sql.c_str());
263 props.broken = m_pDS2->fv(0).get_asString();
265 sql = PrepareSQL("select key,value from addonextra where id=%i", id);
266 m_pDS2->query(sql.c_str());
267 while (!m_pDS2->eof())
269 props.extrainfo.insert(make_pair(m_pDS2->fv(0).get_asString(), m_pDS2->fv(1).get_asString()));
273 sql = PrepareSQL("select addon,version,optional from dependencies where id=%i", id);
274 m_pDS2->query(sql.c_str());
275 while (!m_pDS2->eof())
277 props.dependencies.insert(make_pair(m_pDS2->fv(0).get_asString(), make_pair(AddonVersion(m_pDS2->fv(1).get_asString()), m_pDS2->fv(2).get_asBool())));
281 addon = CAddonMgr::AddonFromProps(props);
282 return NULL != addon.get();
287 CLog::Log(LOGERROR, "%s failed on addon %i", __FUNCTION__, id);
293 bool CAddonDatabase::GetAddons(VECADDONS& addons)
297 if (NULL == m_pDB.get()) return false;
298 if (NULL == m_pDS2.get()) return false;
300 CStdString sql = PrepareSQL("select distinct addonID from addon");
301 m_pDS->query(sql.c_str());
302 while (!m_pDS->eof())
304 sql = PrepareSQL("select id from addon where addonID='%s' order by version desc",m_pDS->fv(0).get_asString().c_str());
305 m_pDS2->query(sql.c_str());
307 if (GetAddon(m_pDS2->fv(0).get_asInt(),addon))
308 addons.push_back(addon);
316 CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
321 void CAddonDatabase::DeleteRepository(const CStdString& id)
325 if (NULL == m_pDB.get()) return;
326 if (NULL == m_pDS.get()) return;
328 CStdString sql = PrepareSQL("select id from repo where addonID='%s'",id.c_str());
329 m_pDS->query(sql.c_str());
331 DeleteRepository(m_pDS->fv(0).get_asInt());
335 CLog::Log(LOGERROR, "%s failed on repo '%s'", __FUNCTION__, id.c_str());
339 void CAddonDatabase::DeleteRepository(int idRepo)
343 if (NULL == m_pDB.get()) return;
344 if (NULL == m_pDS.get()) return;
346 CStdString sql = PrepareSQL("delete from repo where id=%i",idRepo);
347 m_pDS->exec(sql.c_str());
348 sql = PrepareSQL("delete from addon where id in (select idAddon from addonlinkrepo where idRepo=%i)",idRepo);
349 m_pDS->exec(sql.c_str());
350 sql = PrepareSQL("delete from addonextra where id in (select idAddon from addonlinkrepo where idRepo=%i)",idRepo);
351 m_pDS->exec(sql.c_str());
352 sql = PrepareSQL("delete from dependencies where id in (select idAddon from addonlinkrepo where idRepo=%i)",idRepo);
353 m_pDS->exec(sql.c_str());
354 sql = PrepareSQL("delete from addonlinkrepo where idRepo=%i",idRepo);
355 m_pDS->exec(sql.c_str());
360 CLog::Log(LOGERROR, "%s failed on repo %i", __FUNCTION__, idRepo);
364 int CAddonDatabase::AddRepository(const CStdString& id, const VECADDONS& addons, const CStdString& checksum)
368 if (NULL == m_pDB.get()) return -1;
369 if (NULL == m_pDS.get()) return -1;
372 int idRepo = GetRepoChecksum(id,sql);
374 DeleteRepository(idRepo);
378 CDateTime time = CDateTime::GetCurrentDateTime();
379 sql = PrepareSQL("insert into repo (id,addonID,checksum,lastcheck) values (NULL,'%s','%s','%s')",id.c_str(),checksum.c_str(),time.GetAsDBDateTime().c_str());
380 m_pDS->exec(sql.c_str());
381 idRepo = (int)m_pDS->lastinsertid();
382 for (unsigned int i=0;i<addons.size();++i)
383 AddAddon(addons[i],idRepo);
390 CLog::Log(LOGERROR, "%s failed on repo '%s'", __FUNCTION__, id.c_str());
391 RollbackTransaction();
396 int CAddonDatabase::GetRepoChecksum(const std::string& id, std::string& checksum)
400 if (NULL == m_pDB.get()) return -1;
401 if (NULL == m_pDS.get()) return -1;
403 std::string strSQL = PrepareSQL("select * from repo where addonID='%s'",id.c_str());
404 m_pDS->query(strSQL.c_str());
407 checksum = m_pDS->fv("checksum").get_asString();
408 return m_pDS->fv("id").get_asInt();
413 CLog::Log(LOGERROR, "%s failed on repo '%s'", __FUNCTION__, id.c_str());
419 CDateTime CAddonDatabase::GetRepoTimestamp(const CStdString& id)
424 if (NULL == m_pDB.get()) return date;
425 if (NULL == m_pDS.get()) return date;
427 CStdString strSQL = PrepareSQL("select * from repo where addonID='%s'",id.c_str());
428 m_pDS->query(strSQL.c_str());
431 date.SetFromDBDateTime(m_pDS->fv("lastcheck").get_asString());
437 CLog::Log(LOGERROR, "%s failed on repo '%s'", __FUNCTION__, id.c_str());
442 bool CAddonDatabase::SetRepoTimestamp(const CStdString& id, const CStdString& time)
446 if (NULL == m_pDB.get()) return false;
447 if (NULL == m_pDS.get()) return false;
449 CStdString sql = PrepareSQL("update repo set lastcheck='%s' where addonID='%s'",time.c_str(),id.c_str());
450 m_pDS->exec(sql.c_str());
456 CLog::Log(LOGERROR, "%s failed on repo '%s'", __FUNCTION__, id.c_str());
461 bool CAddonDatabase::GetRepository(int id, VECADDONS& addons)
465 if (NULL == m_pDB.get()) return false;
466 if (NULL == m_pDS.get()) return false;
468 CStdString strSQL = PrepareSQL("select * from addonlinkrepo where idRepo=%i",id);
469 m_pDS->query(strSQL.c_str());
470 while (!m_pDS->eof())
473 if (GetAddon(m_pDS->fv("idAddon").get_asInt(),addon))
474 addons.push_back(addon);
481 CLog::Log(LOGERROR, "%s failed on repo %i", __FUNCTION__, id);
486 bool CAddonDatabase::GetRepository(const CStdString& id, VECADDONS& addons)
490 if (NULL == m_pDB.get()) return false;
491 if (NULL == m_pDS.get()) return false;
493 CStdString strSQL = PrepareSQL("select id from repo where addonID='%s'",id.c_str());
494 m_pDS->query(strSQL.c_str());
496 return GetRepository(m_pDS->fv(0).get_asInt(),addons);
500 CLog::Log(LOGERROR, "%s failed on repo %s", __FUNCTION__, id.c_str());
505 bool CAddonDatabase::Search(const CStdString& search, VECADDONS& addons)
509 if (NULL == m_pDB.get()) return false;
510 if (NULL == m_pDS.get()) return false;
513 strSQL=PrepareSQL("SELECT id FROM addon WHERE name LIKE '%%%s%%' OR summary LIKE '%%%s%%' OR description LIKE '%%%s%%'", search.c_str(), search.c_str(), search.c_str());
514 CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
516 if (!m_pDS->query(strSQL.c_str())) return false;
517 if (m_pDS->num_rows() == 0) return false;
519 while (!m_pDS->eof())
522 GetAddon(m_pDS->fv(0).get_asInt(),addon);
523 if (addon->Type() >= ADDON_UNKNOWN+1 && addon->Type() < ADDON_SCRAPER_LIBRARY)
524 addons.push_back(addon);
532 CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
537 void CAddonDatabase::SetPropertiesFromAddon(const AddonPtr& addon,
540 pItem->SetProperty("Addon.ID", addon->ID());
541 pItem->SetProperty("Addon.Type", TranslateType(addon->Type(),true));
542 pItem->SetProperty("Addon.intType", TranslateType(addon->Type()));
543 pItem->SetProperty("Addon.Name", addon->Name());
544 pItem->SetProperty("Addon.Version", addon->Version().c_str());
545 pItem->SetProperty("Addon.Summary", addon->Summary());
546 pItem->SetProperty("Addon.Description", addon->Description());
547 pItem->SetProperty("Addon.Creator", addon->Author());
548 pItem->SetProperty("Addon.Disclaimer", addon->Disclaimer());
549 pItem->SetProperty("Addon.Rating", addon->Stars());
550 CStdString starrating = StringUtils::Format("rating%d.png", addon->Stars());
551 pItem->SetProperty("Addon.StarRating",starrating);
552 pItem->SetProperty("Addon.Path", addon->Path());
553 if (addon->Props().broken == "DEPSNOTMET")
554 pItem->SetProperty("Addon.Broken", g_localizeStrings.Get(24044));
556 pItem->SetProperty("Addon.Broken", addon->Props().broken);
557 std::map<CStdString,CStdString>::iterator it =
558 addon->Props().extrainfo.find("language");
559 if (it != addon->Props().extrainfo.end())
560 pItem->SetProperty("Addon.Language", it->second);
563 bool CAddonDatabase::DisableAddon(const CStdString &addonID, bool disable /* = true */)
567 if (NULL == m_pDB.get()) return false;
568 if (NULL == m_pDS.get()) return false;
572 if (!IsAddonDisabled(addonID)) // Enabled
574 CStdString sql = PrepareSQL("insert into disabled(id, addonID) values(NULL, '%s')", addonID.c_str());
578 // If the addon is a service, stop it
579 if (CAddonMgr::Get().GetAddon(addonID, addon, ADDON_SERVICE, false) && addon)
581 boost::shared_ptr<CService> service = boost::dynamic_pointer_cast<CService>(addon);
585 // restart the pvr manager when disabling a pvr add-on with the pvr manager enabled
586 else if (CAddonMgr::Get().GetAddon(addonID, addon, ADDON_PVRDLL, false) && addon &&
587 PVR::CPVRManager::Get().IsStarted())
588 PVR::CPVRManager::Get().Start(true);
592 return false; // already disabled or failed query
596 bool disabled = IsAddonDisabled(addonID); //we need to know if service addon is running
597 CStdString sql = PrepareSQL("delete from disabled where addonID='%s'", addonID.c_str());
601 // If the addon is a service, start it
602 if (CAddonMgr::Get().GetAddon(addonID, addon, ADDON_SERVICE, false) && addon && disabled)
604 boost::shared_ptr<CService> service = boost::dynamic_pointer_cast<CService>(addon);
608 // (re)start the pvr manager when enabling a pvr add-on
609 else if (CAddonMgr::Get().GetAddon(addonID, addon, ADDON_PVRDLL, false) && addon)
610 PVR::CPVRManager::Get().Start(true);
616 CLog::Log(LOGERROR, "%s failed on addon '%s'", __FUNCTION__, addonID.c_str());
621 bool CAddonDatabase::BreakAddon(const CStdString &addonID, const CStdString& reason)
625 if (NULL == m_pDB.get()) return false;
626 if (NULL == m_pDS.get()) return false;
628 CStdString sql = PrepareSQL("delete from broken where addonID='%s'", addonID.c_str());
631 if (!reason.IsEmpty())
633 sql = PrepareSQL("insert into broken(id, addonID, reason) values(NULL, '%s', '%s')", addonID.c_str(),reason.c_str());
640 CLog::Log(LOGERROR, "%s failed on addon '%s'", __FUNCTION__, addonID.c_str());
645 bool CAddonDatabase::HasAddon(const CStdString &addonID)
647 CStdString strWhereClause = PrepareSQL("addonID = '%s'", addonID.c_str());
648 CStdString strHasAddon = GetSingleValue("addon", "id", strWhereClause);
650 return !strHasAddon.IsEmpty();
653 bool CAddonDatabase::IsAddonDisabled(const CStdString &addonID)
657 if (NULL == m_pDB.get()) return false;
658 if (NULL == m_pDS.get()) return false;
660 CStdString sql = PrepareSQL("select id from disabled where addonID='%s'", addonID.c_str());
661 m_pDS->query(sql.c_str());
662 bool ret = !m_pDS->eof(); // in the disabled table -> disabled
668 CLog::Log(LOGERROR, "%s failed on addon %s", __FUNCTION__, addonID.c_str());
673 bool CAddonDatabase::IsSystemPVRAddonEnabled(const CStdString &addonID)
675 CStdString strWhereClause = PrepareSQL("addonID = '%s'", addonID.c_str());
676 CStdString strEnabled = GetSingleValue("pvrenabled", "id", strWhereClause);
678 return !strEnabled.IsEmpty();
681 CStdString CAddonDatabase::IsAddonBroken(const CStdString &addonID)
685 if (NULL == m_pDB.get()) return "";
686 if (NULL == m_pDS.get()) return "";
688 CStdString sql = PrepareSQL("select reason from broken where addonID='%s'", addonID.c_str());
689 m_pDS->query(sql.c_str());
692 ret = m_pDS->fv(0).get_asString();
698 CLog::Log(LOGERROR, "%s failed on addon %s", __FUNCTION__, addonID.c_str());
703 bool CAddonDatabase::HasDisabledAddons()
707 if (NULL == m_pDB.get()) return false;
708 if (NULL == m_pDS.get()) return false;
710 m_pDS->query("select count(id) from disabled");
711 bool ret = !m_pDS->eof() && m_pDS->fv(0).get_asInt() > 0; // have rows -> have disabled addons
717 CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
722 bool CAddonDatabase::BlacklistAddon(const CStdString& addonID,
723 const CStdString& version)
727 if (NULL == m_pDB.get()) return false;
728 if (NULL == m_pDS.get()) return false;
730 CStdString sql = PrepareSQL("insert into blacklist(id, addonID, version) values(NULL, '%s', '%s')", addonID.c_str(),version.c_str());
737 CLog::Log(LOGERROR, "%s failed on addon '%s' for version '%s'", __FUNCTION__, addonID.c_str(),version.c_str());
742 bool CAddonDatabase::IsAddonBlacklisted(const CStdString& addonID,
743 const CStdString& version)
745 CStdString where = PrepareSQL("addonID='%s' and version='%s'",addonID.c_str(),version.c_str());
746 return !GetSingleValue("blacklist","addonID",where).IsEmpty();
749 bool CAddonDatabase::RemoveAddonFromBlacklist(const CStdString& addonID,
750 const CStdString& version)
754 if (NULL == m_pDB.get()) return false;
755 if (NULL == m_pDS.get()) return false;
757 CStdString sql = PrepareSQL("delete from blacklist where addonID='%s' and version='%s'",addonID.c_str(),version.c_str());
763 CLog::Log(LOGERROR, "%s failed on addon '%s' for version '%s'", __FUNCTION__, addonID.c_str(),version.c_str());