/* * Copyright (C) 2005-2013 Team XBMC * http://xbmc.org * * This Program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This Program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with XBMC; see the file COPYING. If not, see * . * */ #include "AddonDatabase.h" #include "addons/AddonManager.h" #include "utils/log.h" #include "utils/Variant.h" #include "utils/StringUtils.h" #include "XBDateTime.h" #include "dbwrappers/dataset.h" #include "addons/ContextItemAddon.h" using namespace ADDON; using namespace std; CAddonDatabase::CAddonDatabase() { } CAddonDatabase::~CAddonDatabase() { } bool CAddonDatabase::Open() { return CDatabase::Open(); } void CAddonDatabase::CreateTables() { CLog::Log(LOGINFO, "create addon table"); m_pDS->exec("CREATE TABLE addon (id integer primary key, type text," "name text, summary text, description text, stars integer," "path text, addonID text, icon text, version text, " "changelog text, fanart text, author text, disclaimer text," "minversion text)\n"); CLog::Log(LOGINFO, "create addonextra table"); m_pDS->exec("CREATE TABLE addonextra (id integer, key text, value text)\n"); CLog::Log(LOGINFO, "create dependencies table"); m_pDS->exec("CREATE TABLE dependencies (id integer, addon text, version text, optional boolean)\n"); CLog::Log(LOGINFO, "create repo table"); m_pDS->exec("CREATE TABLE repo (id integer primary key, addonID text," "checksum text, lastcheck text, version text)\n"); CLog::Log(LOGINFO, "create addonlinkrepo table"); m_pDS->exec("CREATE TABLE addonlinkrepo (idRepo integer, idAddon integer)\n"); CLog::Log(LOGINFO, "create disabled table"); m_pDS->exec("CREATE TABLE disabled (id integer primary key, addonID text)\n"); CLog::Log(LOGINFO, "create broken table"); m_pDS->exec("CREATE TABLE broken (id integer primary key, addonID text, reason text)\n"); CLog::Log(LOGINFO, "create blacklist table"); m_pDS->exec("CREATE TABLE blacklist (id integer primary key, addonID text, version text)\n"); CLog::Log(LOGINFO, "create package table"); m_pDS->exec("CREATE TABLE package (id integer primary key, addonID text, filename text, hash text)\n"); } void CAddonDatabase::CreateAnalytics() { CLog::Log(LOGINFO, "%s creating indicies", __FUNCTION__); m_pDS->exec("CREATE INDEX idxAddon ON addon(addonID)"); m_pDS->exec("CREATE INDEX idxAddonExtra ON addonextra(id)"); m_pDS->exec("CREATE INDEX idxDependencies ON dependencies(id)"); m_pDS->exec("CREATE UNIQUE INDEX ix_addonlinkrepo_1 ON addonlinkrepo ( idAddon, idRepo )\n"); m_pDS->exec("CREATE UNIQUE INDEX ix_addonlinkrepo_2 ON addonlinkrepo ( idRepo, idAddon )\n"); m_pDS->exec("CREATE UNIQUE INDEX idxDisabled ON disabled(addonID)"); m_pDS->exec("CREATE UNIQUE INDEX idxBroken ON broken(addonID)"); m_pDS->exec("CREATE UNIQUE INDEX idxBlack ON blacklist(addonID)"); m_pDS->exec("CREATE UNIQUE INDEX idxPackage ON package(filename)"); } void CAddonDatabase::UpdateTables(int version) { if (version < 16) { m_pDS->exec("CREATE TABLE package (id integer primary key, addonID text, filename text, hash text)\n"); } if (version < 17) { m_pDS->exec("DELETE FROM repo"); m_pDS->exec("ALTER TABLE repo ADD version text"); } } int CAddonDatabase::AddAddon(const AddonPtr& addon, int idRepo) { try { if (NULL == m_pDB.get()) return -1; if (NULL == m_pDS.get()) return -1; std::string sql = PrepareSQL("insert into addon (id, type, name, summary," "description, stars, path, icon, changelog, " "fanart, addonID, version, author, disclaimer, minversion)" " values(NULL, '%s', '%s', '%s', '%s', %i," "'%s', '%s', '%s', '%s', '%s','%s','%s','%s','%s')", TranslateType(addon->Type(),false).c_str(), addon->Name().c_str(), addon->Summary().c_str(), addon->Description().c_str(),addon->Stars(), addon->Path().c_str(), addon->Props().icon.c_str(), addon->ChangeLog().c_str(),addon->FanArt().c_str(), addon->ID().c_str(), addon->Version().asString().c_str(), addon->Author().c_str(),addon->Disclaimer().c_str(), addon->MinVersion().asString().c_str()); m_pDS->exec(sql.c_str()); int idAddon = (int)m_pDS->lastinsertid(); sql = PrepareSQL("insert into addonlinkrepo (idRepo, idAddon) values (%i,%i)",idRepo,idAddon); m_pDS->exec(sql.c_str()); const InfoMap &info = addon->ExtraInfo(); for (InfoMap::const_iterator i = info.begin(); i != info.end(); ++i) { sql = PrepareSQL("insert into addonextra(id, key, value) values (%i, '%s', '%s')", idAddon, i->first.c_str(), i->second.c_str()); m_pDS->exec(sql.c_str()); } const ADDONDEPS &deps = addon->GetDeps(); for (ADDONDEPS::const_iterator i = deps.begin(); i != deps.end(); ++i) { sql = PrepareSQL("insert into dependencies(id, addon, version, optional) values (%i, '%s', '%s', %i)", idAddon, i->first.c_str(), i->second.first.asString().c_str(), i->second.second ? 1 : 0); m_pDS->exec(sql.c_str()); } return idAddon; } catch (...) { CLog::Log(LOGERROR, "%s failed on addon '%s'", __FUNCTION__, addon->Name().c_str()); } return -1; } AddonVersion CAddonDatabase::GetAddonVersion(const std::string &id) { AddonVersion maxversion("0.0.0"); try { if (NULL == m_pDB.get()) return maxversion; if (NULL == m_pDS2.get()) return maxversion; // there may be multiple addons with this id (eg from different repositories) in the database, // so we want to retrieve the latest version. Order by version won't work as the database // won't know that 1.10 > 1.2, so grab them all and order outside std::string sql = PrepareSQL("select version from addon where addonID='%s'",id.c_str()); m_pDS2->query(sql.c_str()); if (m_pDS2->eof()) return maxversion; while (!m_pDS2->eof()) { AddonVersion version(m_pDS2->fv(0).get_asString()); if (version > maxversion) maxversion = version; m_pDS2->next(); } return maxversion; } catch (...) { CLog::Log(LOGERROR, "%s failed on addon %s", __FUNCTION__, id.c_str()); } return maxversion; } bool CAddonDatabase::GetAddon(const std::string& id, AddonPtr& addon) { try { if (NULL == m_pDB.get()) return false; if (NULL == m_pDS2.get()) return false; // there may be multiple addons with this id (eg from different repositories) in the database, // so we want to retrieve the latest version. Order by version won't work as the database // won't know that 1.10 > 1.2, so grab them all and order outside std::string sql = PrepareSQL("select id,version from addon where addonID='%s'",id.c_str()); m_pDS2->query(sql.c_str()); if (m_pDS2->eof()) return false; AddonVersion maxversion("0.0.0"); int maxid = 0; while (!m_pDS2->eof()) { AddonVersion version(m_pDS2->fv(1).get_asString()); if (version > maxversion) { maxid = m_pDS2->fv(0).get_asInt(); maxversion = version; } m_pDS2->next(); } return GetAddon(maxid,addon); } catch (...) { CLog::Log(LOGERROR, "%s failed on addon %s", __FUNCTION__, id.c_str()); } addon.reset(); return false; } bool CAddonDatabase::GetRepoForAddon(const std::string& addonID, std::string& repo) { try { if (NULL == m_pDB.get()) return false; if (NULL == m_pDS2.get()) return false; std::string 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()); m_pDS2->query(sql.c_str()); if (!m_pDS2->eof()) { repo = m_pDS2->fv(0).get_asString(); m_pDS2->close(); return true; } } catch (...) { CLog::Log(LOGERROR, "%s failed for addon %s", __FUNCTION__, addonID.c_str()); } return false; } bool CAddonDatabase::GetAddon(int id, AddonPtr &addon) { try { if (NULL == m_pDB.get()) return false; if (NULL == m_pDS2.get()) return false; std::string sql = "SELECT addon.*," " broken.reason," " addonextra.key, addonextra.value," " dependencies.addon, dependencies.version, dependencies.optional" " FROM addon" " LEFT JOIN broken" " ON broken.addonID = addon.addonID" " LEFT JOIN addonextra" " ON addonextra.id = addon.id" " LEFT JOIN dependencies" " ON dependencies.id = addon.id"; sql += PrepareSQL(" WHERE addon.id=%i", id); m_pDS2->query(sql.c_str()); if (!m_pDS2->eof()) { const dbiplus::query_data &data = m_pDS2->get_result_set().records; const dbiplus::sql_record* const record = data[0]; AddonProps props(record->at(addon_addonID).get_asString(), TranslateType(record->at(addon_type).get_asString()), record->at(addon_version).get_asString(), record->at(addon_minversion).get_asString()); props.name = record->at(addon_name).get_asString(); props.summary = record->at(addon_summary).get_asString(); props.description = record->at(addon_description).get_asString(); props.changelog = record->at(addon_changelog).get_asString(); props.path = record->at(addon_path).get_asString(); props.icon = record->at(addon_icon).get_asString(); props.fanart = record->at(addon_fanart).get_asString(); props.author = record->at(addon_author).get_asString(); props.disclaimer = record->at(addon_disclaimer).get_asString(); props.broken = record->at(broken_reason).get_asString(); /* while this is a cartesion join and we'll typically get multiple rows, we rely on the fact that extrainfo and dependencies are maps, so insert() will insert the first instance only */ for (dbiplus::query_data::const_iterator i = data.begin(); i != data.end(); ++i) { const dbiplus::sql_record* const record = *i; if (!record->at(addonextra_key).get_asString().empty()) props.extrainfo.insert(make_pair(record->at(addonextra_key).get_asString(), record->at(addonextra_value).get_asString())); if (!m_pDS2->fv(dependencies_addon).get_asString().empty()) props.dependencies.insert(make_pair(record->at(dependencies_addon).get_asString(), make_pair(AddonVersion(record->at(dependencies_version).get_asString()), record->at(dependencies_optional).get_asBool()))); } addon = CAddonMgr::AddonFromProps(props); return NULL != addon.get(); } } catch (...) { CLog::Log(LOGERROR, "%s failed on addon %i", __FUNCTION__, id); } addon.reset(); return false; } bool CAddonDatabase::GetAddons(VECADDONS& addons) { try { if (NULL == m_pDB.get()) return false; if (NULL == m_pDS2.get()) return false; std::string sql = PrepareSQL("select distinct addonID from addon"); m_pDS->query(sql.c_str()); while (!m_pDS->eof()) { AddonPtr addon; if (GetAddon(m_pDS->fv(0).get_asString(),addon)) addons.push_back(addon); m_pDS->next(); } m_pDS->close(); return true; } catch (...) { CLog::Log(LOGERROR, "%s failed", __FUNCTION__); } return false; } void CAddonDatabase::DeleteRepository(const std::string& id) { try { if (NULL == m_pDB.get()) return; if (NULL == m_pDS.get()) return; std::string sql = PrepareSQL("select id from repo where addonID='%s'",id.c_str()); m_pDS->query(sql.c_str()); if (!m_pDS->eof()) DeleteRepository(m_pDS->fv(0).get_asInt()); } catch (...) { CLog::Log(LOGERROR, "%s failed on repo '%s'", __FUNCTION__, id.c_str()); } } void CAddonDatabase::DeleteRepository(int idRepo) { try { if (NULL == m_pDB.get()) return; if (NULL == m_pDS.get()) return; std::string sql = PrepareSQL("delete from repo where id=%i",idRepo); m_pDS->exec(sql.c_str()); sql = PrepareSQL("delete from addon where id in (select idAddon from addonlinkrepo where idRepo=%i)",idRepo); m_pDS->exec(sql.c_str()); sql = PrepareSQL("delete from addonextra where id in (select idAddon from addonlinkrepo where idRepo=%i)",idRepo); m_pDS->exec(sql.c_str()); sql = PrepareSQL("delete from dependencies where id in (select idAddon from addonlinkrepo where idRepo=%i)",idRepo); m_pDS->exec(sql.c_str()); sql = PrepareSQL("delete from addonlinkrepo where idRepo=%i",idRepo); m_pDS->exec(sql.c_str()); } catch (...) { CLog::Log(LOGERROR, "%s failed on repo %i", __FUNCTION__, idRepo); } } int CAddonDatabase::AddRepository(const std::string& id, const VECADDONS& addons, const std::string& checksum, const AddonVersion& version) { try { if (NULL == m_pDB.get()) return -1; if (NULL == m_pDS.get()) return -1; std::string sql; int idRepo = GetRepoChecksum(id,sql); if (idRepo > -1) DeleteRepository(idRepo); BeginTransaction(); CDateTime time = CDateTime::GetCurrentDateTime(); sql = PrepareSQL("insert into repo (id,addonID,checksum,lastcheck,version) values (NULL,'%s','%s','%s','%s')", id.c_str(), checksum.c_str(), time.GetAsDBDateTime().c_str(), version.asString().c_str()); m_pDS->exec(sql.c_str()); idRepo = (int)m_pDS->lastinsertid(); for (unsigned int i=0;iquery(strSQL.c_str()); if (!m_pDS->eof()) { checksum = m_pDS->fv("checksum").get_asString(); return m_pDS->fv("id").get_asInt(); } } catch (...) { CLog::Log(LOGERROR, "%s failed on repo '%s'", __FUNCTION__, id.c_str()); } checksum.clear(); return -1; } CDateTime CAddonDatabase::GetRepoTimestamp(const std::string& id) { CDateTime date; try { if (NULL == m_pDB.get()) return date; if (NULL == m_pDS.get()) return date; std::string strSQL = PrepareSQL("select * from repo where addonID='%s'",id.c_str()); m_pDS->query(strSQL.c_str()); if (!m_pDS->eof()) { date.SetFromDBDateTime(m_pDS->fv("lastcheck").get_asString()); return date; } } catch (...) { CLog::Log(LOGERROR, "%s failed on repo '%s'", __FUNCTION__, id.c_str()); } return date; } AddonVersion CAddonDatabase::GetRepoVersion(const std::string& id) { AddonVersion version("0.0.0"); try { if (NULL == m_pDB.get()) return version; if (NULL == m_pDS2.get()) return version; std::string strSQL = PrepareSQL("select * from repo where addonID='%s'",id.c_str()); m_pDS->query(strSQL.c_str()); if (!m_pDS->eof()) { return AddonVersion(m_pDS->fv("version").get_asString()); } } catch (...) { CLog::Log(LOGERROR, "%s failed on addon %s", __FUNCTION__, id.c_str()); } return version; } bool CAddonDatabase::SetRepoTimestamp(const std::string& id, const std::string& time, const ADDON::AddonVersion& version) { try { if (NULL == m_pDB.get()) return false; if (NULL == m_pDS.get()) return false; std::string sql = PrepareSQL("UPDATE repo SET lastcheck='%s', version='%s' WHERE addonID='%s'", time.c_str(), version.asString().c_str(), id.c_str()); m_pDS->exec(sql.c_str()); return true; } catch (...) { CLog::Log(LOGERROR, "%s failed on repo '%s'", __FUNCTION__, id.c_str()); } return false; } bool CAddonDatabase::GetRepository(int id, VECADDONS& addons) { try { if (NULL == m_pDB.get()) return false; if (NULL == m_pDS.get()) return false; std::string strSQL = PrepareSQL("select * from addonlinkrepo where idRepo=%i",id); m_pDS->query(strSQL.c_str()); while (!m_pDS->eof()) { AddonPtr addon; if (GetAddon(m_pDS->fv("idAddon").get_asInt(),addon)) addons.push_back(addon); m_pDS->next(); } return true; } catch (...) { CLog::Log(LOGERROR, "%s failed on repo %i", __FUNCTION__, id); } return false; } bool CAddonDatabase::GetRepository(const std::string& id, VECADDONS& addons) { try { if (NULL == m_pDB.get()) return false; if (NULL == m_pDS.get()) return false; std::string strSQL = PrepareSQL("select id from repo where addonID='%s'",id.c_str()); m_pDS->query(strSQL.c_str()); if (!m_pDS->eof()) return GetRepository(m_pDS->fv(0).get_asInt(),addons); } catch (...) { CLog::Log(LOGERROR, "%s failed on repo %s", __FUNCTION__, id.c_str()); } return false; } bool CAddonDatabase::Search(const std::string& search, VECADDONS& addons) { try { if (NULL == m_pDB.get()) return false; if (NULL == m_pDS.get()) return false; std::string strSQL; strSQL=PrepareSQL("SELECT addonID FROM addon WHERE name LIKE '%%%s%%' OR summary LIKE '%%%s%%' OR description LIKE '%%%s%%'", search.c_str(), search.c_str(), search.c_str()); CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str()); if (!m_pDS->query(strSQL.c_str())) return false; if (m_pDS->num_rows() == 0) return false; while (!m_pDS->eof()) { AddonPtr addon; GetAddon(m_pDS->fv(0).get_asString(),addon); if (addon->Type() >= ADDON_UNKNOWN+1 && addon->Type() < ADDON_SCRAPER_LIBRARY) addons.push_back(addon); m_pDS->next(); } m_pDS->close(); return true; } catch (...) { CLog::Log(LOGERROR, "%s failed", __FUNCTION__); } return false; } void CAddonDatabase::SetPropertiesFromAddon(const AddonPtr& addon, CFileItemPtr& pItem) { pItem->SetProperty("Addon.ID", addon->ID()); pItem->SetProperty("Addon.Type", TranslateType(addon->Type(),true)); pItem->SetProperty("Addon.intType", TranslateType(addon->Type())); pItem->SetProperty("Addon.Name", addon->Name()); pItem->SetProperty("Addon.Version", addon->Version().asString()); pItem->SetProperty("Addon.Summary", addon->Summary()); pItem->SetProperty("Addon.Description", addon->Description()); pItem->SetProperty("Addon.Creator", addon->Author()); pItem->SetProperty("Addon.Disclaimer", addon->Disclaimer()); pItem->SetProperty("Addon.Rating", addon->Stars()); std::string starrating = StringUtils::Format("rating%d.png", addon->Stars()); pItem->SetProperty("Addon.StarRating",starrating); pItem->SetProperty("Addon.Path", addon->Path()); if (addon->Props().broken == "DEPSNOTMET") pItem->SetProperty("Addon.Broken", g_localizeStrings.Get(24044)); else pItem->SetProperty("Addon.Broken", addon->Props().broken); std::map::iterator it = addon->Props().extrainfo.find("language"); if (it != addon->Props().extrainfo.end()) pItem->SetProperty("Addon.Language", it->second); } bool CAddonDatabase::DisableAddon(const std::string &addonID, bool disable /* = true */) { try { if (NULL == m_pDB.get()) return false; if (NULL == m_pDS.get()) return false; if (disable) { if (!IsAddonDisabled(addonID)) // Enabled { std::string sql = PrepareSQL("insert into disabled(id, addonID) values(NULL, '%s')", addonID.c_str()); m_pDS->exec(sql); // If the addon is a special, call the disabled handler AddonPtr addon; if ((CAddonMgr::Get().GetAddon(addonID, addon, ADDON_SERVICE, false) || CAddonMgr::Get().GetAddon(addonID, addon, ADDON_PVRDLL, false) || CAddonMgr::Get().GetAddon(addonID, addon, ADDON_CONTEXT_ITEM, false)) && addon) addon->OnDisabled(); return true; } return false; // already disabled or failed query } else { bool disabled = IsAddonDisabled(addonID); //we need to know if service addon is running std::string sql = PrepareSQL("delete from disabled where addonID='%s'", addonID.c_str()); m_pDS->exec(sql); if (disabled) { // If the addon is a special, call the enabled handler AddonPtr addon; if ((CAddonMgr::Get().GetAddon(addonID, addon, ADDON_SERVICE, false) || CAddonMgr::Get().GetAddon(addonID, addon, ADDON_PVRDLL, false) || CAddonMgr::Get().GetAddon(addonID, addon, ADDON_CONTEXT_ITEM, false)) && addon) addon->OnEnabled(); } } return true; } catch (...) { CLog::Log(LOGERROR, "%s failed on addon '%s'", __FUNCTION__, addonID.c_str()); } return false; } bool CAddonDatabase::BreakAddon(const std::string &addonID, const std::string& reason) { if (reason.empty()) return ExecuteQuery(PrepareSQL("DELETE FROM broken WHERE addonID='%s'", addonID.c_str())); else return ExecuteQuery(PrepareSQL("REPLACE INTO broken(addonID, reason) VALUES('%s', '%s')", addonID.c_str(), reason.c_str())); } bool CAddonDatabase::HasAddon(const std::string &addonID) { std::string strWhereClause = PrepareSQL("addonID = '%s'", addonID.c_str()); std::string strHasAddon = GetSingleValue("addon", "id", strWhereClause); return !strHasAddon.empty(); } bool CAddonDatabase::IsAddonDisabled(const std::string &addonID) { try { if (NULL == m_pDB.get()) return false; if (NULL == m_pDS.get()) return false; std::string sql = PrepareSQL("select id from disabled where addonID='%s'", addonID.c_str()); m_pDS->query(sql.c_str()); bool ret = !m_pDS->eof(); // in the disabled table -> disabled m_pDS->close(); return ret; } catch (...) { CLog::Log(LOGERROR, "%s failed on addon %s", __FUNCTION__, addonID.c_str()); } return false; } bool CAddonDatabase::IsSystemPVRAddonEnabled(const std::string &addonID) { std::string strWhereClause = PrepareSQL("addonID = '%s'", addonID.c_str()); std::string strEnabled = GetSingleValue("pvrenabled", "id", strWhereClause); return !strEnabled.empty(); } std::string CAddonDatabase::IsAddonBroken(const std::string &addonID) { return GetSingleValue(PrepareSQL("SELECT reason FROM broken WHERE addonID='%s'", addonID.c_str())); } bool CAddonDatabase::HasDisabledAddons() { try { if (NULL == m_pDB.get()) return false; if (NULL == m_pDS.get()) return false; m_pDS->query("select count(id) from disabled"); bool ret = !m_pDS->eof() && m_pDS->fv(0).get_asInt() > 0; // have rows -> have disabled addons m_pDS->close(); return ret; } catch (...) { CLog::Log(LOGERROR, "%s failed", __FUNCTION__); } return false; } bool CAddonDatabase::BlacklistAddon(const std::string& addonID, const std::string& version) { try { if (NULL == m_pDB.get()) return false; if (NULL == m_pDS.get()) return false; std::string sql = PrepareSQL("insert into blacklist(id, addonID, version) values(NULL, '%s', '%s')", addonID.c_str(),version.c_str()); m_pDS->exec(sql); return true; } catch (...) { CLog::Log(LOGERROR, "%s failed on addon '%s' for version '%s'", __FUNCTION__, addonID.c_str(),version.c_str()); } return false; } bool CAddonDatabase::IsAddonBlacklisted(const std::string& addonID, const std::string& version) { std::string where = PrepareSQL("addonID='%s' and version='%s'",addonID.c_str(),version.c_str()); return !GetSingleValue("blacklist","addonID",where).empty(); } bool CAddonDatabase::RemoveAddonFromBlacklist(const std::string& addonID, const std::string& version) { try { if (NULL == m_pDB.get()) return false; if (NULL == m_pDS.get()) return false; std::string sql = PrepareSQL("delete from blacklist where addonID='%s' and version='%s'",addonID.c_str(),version.c_str()); m_pDS->exec(sql); return true; } catch (...) { CLog::Log(LOGERROR, "%s failed on addon '%s' for version '%s'", __FUNCTION__, addonID.c_str(),version.c_str()); } return false; } bool CAddonDatabase::AddPackage(const std::string& addonID, const std::string& packageFileName, const std::string& hash) { std::string sql = PrepareSQL("insert into package(id, addonID, filename, hash)" "values(NULL, '%s', '%s', '%s')", addonID.c_str(), packageFileName.c_str(), hash.c_str()); return ExecuteQuery(sql); } bool CAddonDatabase::GetPackageHash(const std::string& addonID, const std::string& packageFileName, std::string& hash) { std::string where = PrepareSQL("addonID='%s' and filename='%s'", addonID.c_str(), packageFileName.c_str()); hash = GetSingleValue("package", "hash", where); return !hash.empty(); } bool CAddonDatabase::RemovePackage(const std::string& packageFileName) { std::string sql = PrepareSQL("delete from package where filename='%s'", packageFileName.c_str()); return ExecuteQuery(sql); }