[Windows] Fix driver version detection of AMD RDNA+ GPU on Windows 10
[xbmc.git] / xbmc / addons / AddonDatabase.cpp
blob29837be2f6639a2bcd7397d8454d368fd7188f7a
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "AddonDatabase.h"
11 #include "XBDateTime.h"
12 #include "addons/AddonBuilder.h"
13 #include "addons/addoninfo/AddonInfo.h"
14 #include "addons/addoninfo/AddonInfoBuilder.h"
15 #include "addons/addoninfo/AddonType.h"
16 #include "dbwrappers/dataset.h"
17 #include "filesystem/SpecialProtocol.h"
18 #include "utils/JSONVariantParser.h"
19 #include "utils/JSONVariantWriter.h"
20 #include "utils/StringUtils.h"
21 #include "utils/Variant.h"
22 #include "utils/log.h"
24 #include <algorithm>
25 #include <iterator>
26 #include <utility>
28 using namespace ADDON;
30 std::string CAddonDatabaseSerializer::SerializeMetadata(const CAddonInfo& addon)
32 CVariant variant;
33 variant["author"] = addon.Author();
34 variant["disclaimer"] = addon.Disclaimer();
35 variant["lifecycletype"] = static_cast<unsigned int>(addon.LifecycleState());
36 variant["lifecycledesc"] = addon.LifecycleStateDescription();
37 variant["size"] = addon.PackageSize();
39 variant["path"] = addon.Path();
40 variant["icon"] = addon.Icon();
42 variant["art"] = CVariant(CVariant::VariantTypeObject);
43 for (const auto& item : addon.Art())
44 variant["art"][item.first] = item.second;
46 variant["screenshots"] = CVariant(CVariant::VariantTypeArray);
47 for (const auto& item : addon.Screenshots())
48 variant["screenshots"].push_back(item);
50 variant["extensions"] = CVariant(CVariant::VariantTypeArray);
51 variant["extensions"].push_back(SerializeExtensions(*addon.Type(addon.MainType())));
53 variant["dependencies"] = CVariant(CVariant::VariantTypeArray);
54 for (const auto& dep : addon.GetDependencies())
56 CVariant info(CVariant::VariantTypeObject);
57 info["addonId"] = dep.id;
58 info["version"] = dep.version.asString();
59 info["minversion"] = dep.versionMin.asString();
60 info["optional"] = dep.optional;
61 variant["dependencies"].push_back(std::move(info));
64 variant["extrainfo"] = CVariant(CVariant::VariantTypeArray);
65 for (const auto& kv : addon.ExtraInfo())
67 CVariant info(CVariant::VariantTypeObject);
68 info["key"] = kv.first;
69 info["value"] = kv.second;
70 variant["extrainfo"].push_back(std::move(info));
73 std::string json;
74 CJSONVariantWriter::Write(variant, json, true);
75 return json;
78 CVariant CAddonDatabaseSerializer::SerializeExtensions(const CAddonExtensions& addonType)
80 CVariant variant;
81 variant["type"] = addonType.m_point;
83 variant["values"] = CVariant(CVariant::VariantTypeArray);
84 for (const auto& value : addonType.m_values)
86 CVariant info(CVariant::VariantTypeObject);
87 info["id"] = value.first;
88 info["content"] = CVariant(CVariant::VariantTypeArray);
89 for (const auto& content : value.second)
91 CVariant contentEntry(CVariant::VariantTypeObject);
92 contentEntry["key"] = content.first;
93 contentEntry["value"] = content.second.str;
94 info["content"].push_back(std::move(contentEntry));
97 variant["values"].push_back(std::move(info));
100 variant["children"] = CVariant(CVariant::VariantTypeArray);
101 for (auto& child : addonType.m_children)
103 CVariant info(CVariant::VariantTypeObject);
104 info["id"] = child.first;
105 info["child"] = SerializeExtensions(child.second);
106 variant["children"].push_back(std::move(info));
109 return variant;
112 void CAddonDatabaseSerializer::DeserializeMetadata(const std::string& document,
113 CAddonInfoBuilderFromDB& builder)
115 CVariant variant;
116 if (!CJSONVariantParser::Parse(document, variant))
117 return;
119 builder.SetAuthor(variant["author"].asString());
120 builder.SetDisclaimer(variant["disclaimer"].asString());
121 builder.SetLifecycleState(
122 static_cast<AddonLifecycleState>(variant["lifecycletype"].asUnsignedInteger()),
123 variant["lifecycledesc"].asString());
124 builder.SetPackageSize(variant["size"].asUnsignedInteger());
126 builder.SetPath(variant["path"].asString());
127 builder.SetIcon(variant["icon"].asString());
129 std::map<std::string, std::string> art;
130 for (auto it = variant["art"].begin_map(); it != variant["art"].end_map(); ++it)
131 art.emplace(it->first, it->second.asString());
132 builder.SetArt(std::move(art));
134 std::vector<std::string> screenshots;
135 for (auto it = variant["screenshots"].begin_array(); it != variant["screenshots"].end_array(); ++it)
136 screenshots.push_back(it->asString());
137 builder.SetScreenshots(std::move(screenshots));
139 CAddonType addonType;
140 DeserializeExtensions(variant["extensions"][0], addonType);
141 addonType.m_type = CAddonInfo::TranslateType(addonType.m_point);
142 builder.SetExtensions(std::move(addonType));
145 std::vector<DependencyInfo> deps;
146 for (auto it = variant["dependencies"].begin_array(); it != variant["dependencies"].end_array(); ++it)
148 deps.emplace_back((*it)["addonId"].asString(), CAddonVersion((*it)["minversion"].asString()),
149 CAddonVersion((*it)["version"].asString()), (*it)["optional"].asBoolean());
151 builder.SetDependencies(std::move(deps));
154 InfoMap extraInfo;
155 for (auto it = variant["extrainfo"].begin_array(); it != variant["extrainfo"].end_array(); ++it)
156 extraInfo.emplace((*it)["key"].asString(), (*it)["value"].asString());
157 builder.SetExtrainfo(std::move(extraInfo));
160 void CAddonDatabaseSerializer::DeserializeExtensions(const CVariant& variant,
161 CAddonExtensions& addonType)
163 addonType.m_point = variant["type"].asString();
165 for (auto value = variant["values"].begin_array(); value != variant["values"].end_array();
166 ++value)
168 std::string id = (*value)["id"].asString();
169 std::vector<std::pair<std::string, SExtValue>> extValues;
170 for (auto content = (*value)["content"].begin_array();
171 content != (*value)["content"].end_array(); ++content)
173 extValues.emplace_back((*content)["key"].asString(), SExtValue{(*content)["value"].asString()});
176 addonType.m_values.emplace_back(id, extValues);
179 for (auto child = variant["children"].begin_array(); child != variant["children"].end_array();
180 ++child)
182 CAddonExtensions childExt;
183 DeserializeExtensions((*child)["child"], childExt);
184 std::string id = (*child)["id"].asString();
185 addonType.m_children.emplace_back(id, childExt);
188 return;
191 CAddonDatabase::CAddonDatabase() = default;
193 CAddonDatabase::~CAddonDatabase() = default;
195 bool CAddonDatabase::Open()
197 return CDatabase::Open();
200 int CAddonDatabase::GetMinSchemaVersion() const
202 return 21;
205 int CAddonDatabase::GetSchemaVersion() const
207 return 33;
210 void CAddonDatabase::CreateTables()
212 CLog::Log(LOGINFO, "create addons table");
213 m_pDS->exec("CREATE TABLE addons ("
214 "id INTEGER PRIMARY KEY,"
215 "metadata BLOB,"
216 "addonID TEXT NOT NULL,"
217 "version TEXT NOT NULL,"
218 "name TEXT NOT NULL,"
219 "summary TEXT NOT NULL,"
220 "news TEXT NOT NULL,"
221 "description TEXT NOT NULL)");
223 CLog::Log(LOGINFO, "create repo table");
224 m_pDS->exec("CREATE TABLE repo (id integer primary key, addonID text,"
225 "checksum text, lastcheck text, version text, nextcheck TEXT)\n");
227 CLog::Log(LOGINFO, "create addonlinkrepo table");
228 m_pDS->exec("CREATE TABLE addonlinkrepo (idRepo integer, idAddon integer)\n");
230 CLog::Log(LOGINFO, "create update_rules table");
231 m_pDS->exec(
232 "CREATE TABLE update_rules (id integer primary key, addonID TEXT, updateRule INTEGER)\n");
234 CLog::Log(LOGINFO, "create package table");
235 m_pDS->exec("CREATE TABLE package (id integer primary key, addonID text, filename text, hash text)\n");
237 CLog::Log(LOGINFO, "create installed table");
238 m_pDS->exec("CREATE TABLE installed (id INTEGER PRIMARY KEY, addonID TEXT UNIQUE, "
239 "enabled BOOLEAN, installDate TEXT, lastUpdated TEXT, lastUsed TEXT, "
240 "origin TEXT NOT NULL DEFAULT '', disabledReason INTEGER NOT NULL DEFAULT 0) \n");
243 void CAddonDatabase::CreateAnalytics()
245 CLog::Log(LOGINFO, "{} creating indices", __FUNCTION__);
246 m_pDS->exec("CREATE INDEX idxAddons ON addons(addonID)");
247 m_pDS->exec("CREATE UNIQUE INDEX ix_addonlinkrepo_1 ON addonlinkrepo ( idAddon, idRepo )\n");
248 m_pDS->exec("CREATE UNIQUE INDEX ix_addonlinkrepo_2 ON addonlinkrepo ( idRepo, idAddon )\n");
249 m_pDS->exec("CREATE UNIQUE INDEX idxUpdate_rules ON update_rules(addonID, updateRule)");
250 m_pDS->exec("CREATE UNIQUE INDEX idxPackage ON package(filename)");
253 void CAddonDatabase::UpdateTables(int version)
255 if (version < 22)
257 m_pDS->exec("DROP TABLE system");
259 if (version < 24)
261 m_pDS->exec("DELETE FROM addon");
262 m_pDS->exec("DELETE FROM addonextra");
263 m_pDS->exec("DELETE FROM dependencies");
264 m_pDS->exec("DELETE FROM addonlinkrepo");
265 m_pDS->exec("DELETE FROM repo");
267 if (version < 25)
269 m_pDS->exec("ALTER TABLE installed ADD origin TEXT NOT NULL DEFAULT ''");
271 if (version < 26)
273 m_pDS->exec("DROP TABLE addon");
274 m_pDS->exec("DROP TABLE addonextra");
275 m_pDS->exec("DROP TABLE dependencies");
276 m_pDS->exec("DELETE FROM addonlinkrepo");
277 m_pDS->exec("DELETE FROM repo");
278 m_pDS->exec("CREATE TABLE addons ("
279 "id INTEGER PRIMARY KEY,"
280 "metadata BLOB,"
281 "addonID TEXT NOT NULL,"
282 "version TEXT NOT NULL,"
283 "name TEXT NOT NULL,"
284 "summary TEXT NOT NULL,"
285 "description TEXT NOT NULL)");
287 if (version < 27)
289 m_pDS->exec("ALTER TABLE addons ADD news TEXT NOT NULL DEFAULT ''");
291 if (version < 28)
293 m_pDS->exec("ALTER TABLE installed ADD disabledReason INTEGER NOT NULL DEFAULT 0");
294 // On adding this field we will use user disabled as the default reason for any disabled addons
295 m_pDS->exec("UPDATE installed SET disabledReason=1 WHERE enabled=0");
297 if (version < 29)
299 m_pDS->exec("DROP TABLE broken");
301 if (version < 30)
303 m_pDS->exec("ALTER TABLE repo ADD nextcheck TEXT");
305 if (version < 31)
307 m_pDS->exec("UPDATE installed SET origin = addonID WHERE (origin='') AND "
308 "EXISTS (SELECT * FROM repo WHERE repo.addonID = installed.addonID)");
310 if (version < 32)
312 m_pDS->exec(
313 "CREATE TABLE update_rules (id integer primary key, addonID text, updateRule INTEGER)");
314 m_pDS->exec("INSERT INTO update_rules (addonID, updateRule) SELECT addonID, 1 updateRule FROM "
315 "blacklist");
316 m_pDS->exec("DROP INDEX IF EXISTS idxBlack");
317 m_pDS->exec("DROP TABLE blacklist");
319 if (version < 33)
321 m_pDS->query(PrepareSQL("SELECT * FROM addons"));
322 while (!m_pDS->eof())
324 const int id = m_pDS->fv("id").get_asInt();
325 const std::string metadata = m_pDS->fv("metadata").get_asString();
327 CVariant variant;
328 if (!CJSONVariantParser::Parse(metadata, variant))
329 continue;
331 // Replace obsolete "broken" with new in json text
332 if (variant.isMember("broken") && variant["broken"].asString().empty())
334 variant["lifecycletype"] = static_cast<unsigned int>(AddonLifecycleState::BROKEN);
335 variant["lifecycledesc"] = variant["broken"].asString();
336 variant.erase("broken");
339 // Fix wrong added change about "broken" to "lifecycle..." (request 18286)
340 // as there was every addon marked as broken. This checks his text and if
341 // them empty, becomes it declared as normal.
342 if (variant.isMember("lifecycledesc") && variant.isMember("lifecycletype") &&
343 variant["lifecycledesc"].asString().empty() &&
344 variant["lifecycletype"].asUnsignedInteger() !=
345 static_cast<unsigned int>(AddonLifecycleState::NORMAL))
347 variant["lifecycletype"] = static_cast<unsigned int>(AddonLifecycleState::NORMAL);
350 CVariant variantUpdate;
351 variantUpdate["type"] = variant["extensions"][0].asString();
352 variantUpdate["values"] = CVariant(CVariant::VariantTypeArray);
353 variantUpdate["children"] = CVariant(CVariant::VariantTypeArray);
355 for (auto it = variant["extrainfo"].begin_array(); it != variant["extrainfo"].end_array();
356 ++it)
358 if ((*it)["key"].asString() == "provides")
360 CVariant info(CVariant::VariantTypeObject);
361 info["id"] = (*it)["key"].asString();
362 info["content"] = CVariant(CVariant::VariantTypeArray);
364 CVariant contentEntry(CVariant::VariantTypeObject);
365 contentEntry["key"] = (*it)["key"].asString();
366 contentEntry["value"] = (*it)["value"].asString();
367 info["content"].push_back(std::move(contentEntry));
369 variantUpdate["values"].push_back(std::move(info));
370 break;
373 variant["extensions"][0] = variantUpdate;
375 std::string json;
376 CJSONVariantWriter::Write(variant, json, true);
377 m_pDS->exec(PrepareSQL("UPDATE addons SET metadata='%s' WHERE id=%i", json.c_str(), id));
379 m_pDS->next();
381 m_pDS->close();
385 void CAddonDatabase::SyncInstalled(const std::set<std::string>& ids,
386 const std::set<std::string>& system,
387 const std::set<std::string>& optional)
391 if (!m_pDB)
392 return;
393 if (!m_pDS)
394 return;
396 std::set<std::string> db;
397 m_pDS->query(PrepareSQL("SELECT addonID FROM installed"));
398 while (!m_pDS->eof())
400 db.insert(m_pDS->fv("addonID").get_asString());
401 m_pDS->next();
403 m_pDS->close();
405 std::set<std::string> added;
406 std::set<std::string> removed;
407 std::set_difference(ids.begin(), ids.end(), db.begin(), db.end(), std::inserter(added, added.end()));
408 std::set_difference(db.begin(), db.end(), ids.begin(), ids.end(), std::inserter(removed, removed.end()));
410 for (const auto& id : added)
411 CLog::Log(LOGDEBUG, "CAddonDatabase: {} has been installed.", id);
413 for (const auto& id : removed)
414 CLog::Log(LOGDEBUG, "CAddonDatabase: {} has been uninstalled.", id);
416 std::string now = CDateTime::GetCurrentDateTime().GetAsDBDateTime();
417 BeginTransaction();
418 for (const auto& id : added)
420 int enable = 0;
422 if (system.find(id) != system.end() || optional.find(id) != optional.end())
423 enable = 1;
425 m_pDS->exec(PrepareSQL("INSERT INTO installed(addonID, enabled, installDate) "
426 "VALUES('%s', %d, '%s')", id.c_str(), enable, now.c_str()));
429 for (const auto& id : removed)
431 m_pDS->exec(PrepareSQL("DELETE FROM installed WHERE addonID='%s'", id.c_str()));
432 RemoveAllUpdateRulesForAddon(id);
433 DeleteRepository(id);
436 for (const auto& id : system)
438 m_pDS->exec(PrepareSQL("UPDATE installed SET enabled=1 WHERE addonID='%s'", id.c_str()));
439 // Make sure system addons always have ORIGIN_SYSTEM origin
440 m_pDS->exec(PrepareSQL("UPDATE installed SET origin='%s' WHERE addonID='%s'", ORIGIN_SYSTEM,
441 id.c_str()));
444 for (const auto& id : optional)
446 // Make sure optional system addons always have ORIGIN_SYSTEM origin
447 m_pDS->exec(PrepareSQL("UPDATE installed SET origin='%s' WHERE addonID='%s'", ORIGIN_SYSTEM,
448 id.c_str()));
451 CommitTransaction();
453 catch (...)
455 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
456 RollbackTransaction();
460 bool CAddonDatabase::SetLastUpdated(const std::string& addonId, const CDateTime& dateTime)
464 if (!m_pDB)
465 return false;
466 if (!m_pDS)
467 return false;
469 m_pDS->exec(PrepareSQL("UPDATE installed SET lastUpdated='%s' WHERE addonID='%s'",
470 dateTime.GetAsDBDateTime().c_str(), addonId.c_str()));
471 return true;
473 catch (...)
475 CLog::Log(LOGERROR, "{} failed on addon '{}'", __FUNCTION__, addonId);
477 return false;
480 bool CAddonDatabase::SetOrigin(const std::string& addonId, const std::string& origin)
484 if (!m_pDB)
485 return false;
486 if (!m_pDS)
487 return false;
489 m_pDS->exec(PrepareSQL("UPDATE installed SET origin='%s' WHERE addonID='%s'", origin.c_str(), addonId.c_str()));
490 return true;
492 catch (...)
494 CLog::Log(LOGERROR, "{} failed on addon '{}'", __FUNCTION__, addonId);
496 return false;
499 bool CAddonDatabase::SetLastUsed(const std::string& addonId, const CDateTime& dateTime)
503 if (!m_pDB)
504 return false;
505 if (!m_pDS)
506 return false;
508 auto start = std::chrono::steady_clock::now();
509 m_pDS->exec(PrepareSQL("UPDATE installed SET lastUsed='%s' WHERE addonID='%s'",
510 dateTime.GetAsDBDateTime().c_str(), addonId.c_str()));
512 auto end = std::chrono::steady_clock::now();
513 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
514 CLog::Log(LOGDEBUG, "CAddonDatabase::SetLastUsed[{}] took {} ms", addonId, duration.count());
515 return true;
517 catch (...)
519 CLog::Log(LOGERROR, "{} failed on addon '{}'", __FUNCTION__, addonId);
521 return false;
524 bool CAddonDatabase::FindByAddonId(const std::string& addonId, ADDON::VECADDONS& result) const
528 if (!m_pDB)
529 return false;
530 if (!m_pDS)
531 return false;
533 std::string sql = PrepareSQL(
534 "SELECT addons.version, addons.name, addons.summary, addons.description, addons.metadata, addons.news,"
535 "repo.addonID AS repoID FROM addons "
536 "JOIN addonlinkrepo ON addonlinkrepo.idAddon=addons.id "
537 "JOIN repo ON repo.id=addonlinkrepo.idRepo "
538 "WHERE "
539 "repo.checksum IS NOT NULL AND repo.checksum != '' "
540 "AND EXISTS (SELECT * FROM installed WHERE installed.addonID=repoID AND installed.enabled=1) "
541 "AND addons.addonID='%s'", addonId.c_str());
543 m_pDS->query(sql);
545 VECADDONS addons;
546 addons.reserve(m_pDS->num_rows());
548 while (!m_pDS->eof())
550 CAddonInfoBuilderFromDB builder;
551 builder.SetId(addonId);
552 builder.SetVersion(CAddonVersion(m_pDS->fv("version").get_asString()));
553 builder.SetName(m_pDS->fv("name").get_asString());
554 builder.SetSummary(m_pDS->fv("summary").get_asString());
555 builder.SetDescription(m_pDS->fv("description").get_asString());
556 CAddonDatabaseSerializer::DeserializeMetadata(m_pDS->fv("metadata").get_asString(), builder);
557 builder.SetChangelog(m_pDS->fv("news").get_asString());
558 builder.SetOrigin(m_pDS->fv("repoID").get_asString());
560 auto addon = CAddonBuilder::Generate(builder.get(), AddonType::UNKNOWN);
561 if (addon)
562 addons.push_back(std::move(addon));
563 else
564 CLog::Log(LOGERROR, "CAddonDatabase: failed to build {}", addonId);
565 m_pDS->next();
567 m_pDS->close();
568 result = std::move(addons);
569 return true;
571 catch (...)
573 CLog::Log(LOGERROR, "{} failed on addon {}", __FUNCTION__, addonId);
575 return false;
578 bool CAddonDatabase::GetAddon(const std::string& addonID,
579 const CAddonVersion& version,
580 const std::string& repoId,
581 AddonPtr& addon)
585 if (!m_pDB)
586 return false;
587 if (!m_pDS)
588 return false;
590 const std::string sql =
591 PrepareSQL("SELECT addons.id FROM addons "
592 "JOIN addonlinkrepo ON addonlinkrepo.idAddon=addons.id "
593 "JOIN repo ON repo.id=addonlinkrepo.idRepo "
594 "WHERE addons.addonID='%s' AND addons.version='%s' AND repo.addonID='%s'",
595 addonID.c_str(), version.asString().c_str(), repoId.c_str());
597 m_pDS->query(sql);
598 if (m_pDS->eof())
599 return false;
601 return GetAddon(m_pDS->fv("id").get_asInt(), addon);
603 catch (...)
605 CLog::Log(LOGERROR, "{} failed on addon {}", __FUNCTION__, addonID);
607 return false;
611 bool CAddonDatabase::GetAddon(int id, AddonPtr &addon)
615 if (!m_pDB)
616 return false;
617 if (!m_pDS2)
618 return false;
620 m_pDS2->query(PrepareSQL("SELECT addons.*, repo.addonID as origin FROM addons "
621 "JOIN addonlinkrepo ON addonlinkrepo.idAddon=addons.id "
622 "JOIN repo ON repo.id=addonlinkrepo.idRepo "
623 "WHERE addons.id=%i",
624 id));
625 if (m_pDS2->eof())
626 return false;
628 CAddonInfoBuilderFromDB builder;
629 builder.SetId(m_pDS2->fv("addonID").get_asString());
630 builder.SetOrigin(m_pDS2->fv("origin").get_asString());
631 builder.SetVersion(CAddonVersion(m_pDS2->fv("version").get_asString()));
632 builder.SetName(m_pDS2->fv("name").get_asString());
633 builder.SetSummary(m_pDS2->fv("summary").get_asString());
634 builder.SetDescription(m_pDS2->fv("description").get_asString());
635 CAddonDatabaseSerializer::DeserializeMetadata(m_pDS2->fv("metadata").get_asString(), builder);
637 addon = CAddonBuilder::Generate(builder.get(), AddonType::UNKNOWN);
638 return addon != nullptr;
641 catch (...)
643 CLog::Log(LOGERROR, "{} failed on addon {}", __FUNCTION__, id);
645 return false;
648 bool CAddonDatabase::GetRepositoryContent(VECADDONS& addons) const
650 return GetRepositoryContent("", addons);
653 bool CAddonDatabase::GetRepositoryContent(const std::string& id, VECADDONS& addons) const
657 if (!m_pDB)
658 return false;
659 if (!m_pDS)
660 return false;
662 auto start = std::chrono::steady_clock::now();
664 // Ensure that the repositories we fetch from are enabled and valid.
665 std::vector<std::string> repoIds;
667 std::string sql = PrepareSQL(
668 " SELECT repo.id FROM repo"
669 " WHERE repo.checksum IS NOT NULL AND repo.checksum != ''"
670 " AND EXISTS (SELECT * FROM installed WHERE installed.addonID=repo.addonID AND"
671 " installed.enabled=1)");
673 if (!id.empty())
674 sql += PrepareSQL(" AND repo.addonId='%s'", id.c_str());
676 m_pDS->query(sql);
677 while (!m_pDS->eof())
679 repoIds.emplace_back(m_pDS->fv("id").get_asString());
680 m_pDS->next();
684 auto end = std::chrono::steady_clock::now();
685 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
686 CLog::Log(LOGDEBUG, "CAddonDatabase: SELECT repo.id FROM repo .. took {} ms", duration.count());
688 if (repoIds.empty())
690 if (id.empty())
692 CLog::Log(LOGDEBUG, "CAddonDatabase: no valid repository, continuing");
693 addons = {};
694 return true;
697 CLog::Log(LOGDEBUG, "CAddonDatabase: no valid repository matching '{}'", id);
698 return false;
702 std::string sql = PrepareSQL(" SELECT addons.*, repo.addonID AS repoID FROM addons"
703 " JOIN addonlinkrepo ON addons.id=addonlinkrepo.idAddon"
704 " JOIN repo ON repo.id=addonlinkrepo.idRepo"
705 " WHERE addonlinkrepo.idRepo IN (%s)"
706 " ORDER BY repo.addonID, addons.addonID",
707 StringUtils::Join(repoIds, ",").c_str());
709 start = std::chrono::steady_clock::now();
710 m_pDS->query(sql);
712 end = std::chrono::steady_clock::now();
713 duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
714 CLog::Log(LOGDEBUG, "CAddonDatabase: query {} returned {} rows in {} ms", sql,
715 m_pDS->num_rows(), duration.count());
718 VECADDONS result;
719 result.reserve(m_pDS->num_rows());
721 while (!m_pDS->eof())
723 std::string addonId = m_pDS->fv("addonID").get_asString();
724 CAddonVersion version(m_pDS->fv("version").get_asString());
726 CAddonInfoBuilderFromDB builder;
727 builder.SetId(addonId);
728 builder.SetVersion(version);
729 builder.SetName(m_pDS->fv("name").get_asString());
730 builder.SetSummary(m_pDS->fv("summary").get_asString());
731 builder.SetDescription(m_pDS->fv("description").get_asString());
732 builder.SetOrigin(m_pDS->fv("repoID").get_asString());
733 CAddonDatabaseSerializer::DeserializeMetadata(m_pDS->fv("metadata").get_asString(), builder);
735 auto addon = CAddonBuilder::Generate(builder.get(), AddonType::UNKNOWN);
736 if (addon)
738 result.emplace_back(std::move(addon));
740 else
741 CLog::Log(LOGWARNING, "CAddonDatabase: failed to build {}", addonId);
742 m_pDS->next();
744 m_pDS->close();
745 addons = std::move(result);
747 end = std::chrono::steady_clock::now();
748 duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
749 CLog::Log(LOGDEBUG, "CAddonDatabase::GetAddons took {} ms", duration.count());
751 return true;
753 catch (...)
755 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
757 return false;
760 void CAddonDatabase::DeleteRepository(const std::string& id)
764 if (!m_pDB)
765 return;
766 if (!m_pDS)
767 return;
769 int idRepo = GetRepositoryId(id);
770 if (idRepo < 0)
771 return;
773 m_pDS->exec(PrepareSQL("DELETE FROM repo WHERE id=%i", idRepo));
775 catch (...)
777 CLog::Log(LOGERROR, "{} failed on repo '{}'", __FUNCTION__, id);
781 void CAddonDatabase::DeleteRepositoryContents(const std::string& id)
785 if (!m_pDB)
786 return;
787 if (!m_pDS)
788 return;
790 int idRepo = GetRepositoryId(id);
791 if (idRepo < 0)
792 return;
794 m_pDS->exec(PrepareSQL("DELETE FROM addons WHERE id IN (SELECT idAddon FROM addonlinkrepo WHERE idRepo=%i)", idRepo));
795 m_pDS->exec(PrepareSQL("DELETE FROM addonlinkrepo WHERE idRepo=%i", idRepo));
797 catch (...)
799 CLog::Log(LOGERROR, "{} failed on repo '{}'", __FUNCTION__, id);
803 int CAddonDatabase::GetRepositoryId(const std::string& addonId)
805 if (!m_pDB)
806 return -1;
807 if (!m_pDS)
808 return -1;
810 m_pDS->query(PrepareSQL("SELECT id FROM repo WHERE addonID='%s'", addonId.c_str()));
811 if (m_pDS->eof())
812 return -1;
814 return m_pDS->fv("id").get_asInt();
817 bool CAddonDatabase::UpdateRepositoryContent(const std::string& repository,
818 const CAddonVersion& version,
819 const std::string& checksum,
820 const std::vector<AddonInfoPtr>& addons)
824 if (!m_pDB)
825 return false;
826 if (!m_pDS)
827 return false;
829 DeleteRepositoryContents(repository);
830 int idRepo = GetRepositoryId(repository);
831 if (idRepo < 0)
832 return false;
834 assert(idRepo > 0);
836 m_pDB->start_transaction();
837 m_pDS->exec(
838 PrepareSQL("UPDATE repo SET checksum='%s' WHERE id='%i'", checksum.c_str(), idRepo));
839 for (const auto& addon : addons)
841 m_pDS->exec(PrepareSQL(
842 "INSERT INTO addons (id, metadata, addonID, version, name, summary, description, news) "
843 "VALUES (NULL, '%s', '%s', '%s', '%s','%s', '%s','%s')",
844 CAddonDatabaseSerializer::SerializeMetadata(*addon).c_str(), addon->ID().c_str(),
845 addon->Version().asString().c_str(), addon->Name().c_str(), addon->Summary().c_str(),
846 addon->Description().c_str(), addon->ChangeLog().c_str()));
848 int idAddon = static_cast<int>(m_pDS->lastinsertid());
849 if (idAddon <= 0)
851 CLog::Log(LOGERROR, "{} insert failed on addon '{}'", __FUNCTION__, addon->ID());
852 RollbackTransaction();
853 return false;
856 m_pDS->exec(PrepareSQL("INSERT INTO addonlinkrepo (idRepo, idAddon) VALUES (%i, %i)", idRepo, idAddon));
859 m_pDB->commit_transaction();
860 return true;
862 catch (...)
864 CLog::Log(LOGERROR, "{} failed on repo '{}'", __FUNCTION__, repository);
865 RollbackTransaction();
867 return false;
870 int CAddonDatabase::GetRepoChecksum(const std::string& id, std::string& checksum)
874 if (!m_pDB)
875 return -1;
876 if (!m_pDS)
877 return -1;
879 std::string strSQL = PrepareSQL("select * from repo where addonID='%s'",id.c_str());
880 m_pDS->query(strSQL);
881 if (!m_pDS->eof())
883 checksum = m_pDS->fv("checksum").get_asString();
884 return m_pDS->fv("id").get_asInt();
887 catch (...)
889 CLog::Log(LOGERROR, "{} failed on repo '{}'", __FUNCTION__, id);
891 checksum.clear();
892 return -1;
895 CAddonDatabase::RepoUpdateData CAddonDatabase::GetRepoUpdateData(const std::string& id)
897 RepoUpdateData result{};
900 if (m_pDB && m_pDS)
902 std::string strSQL = PrepareSQL("select * from repo where addonID='%s'",id.c_str());
903 m_pDS->query(strSQL);
904 if (!m_pDS->eof())
906 result.lastCheckedAt.SetFromDBDateTime(m_pDS->fv("lastcheck").get_asString());
907 result.lastCheckedVersion = CAddonVersion(m_pDS->fv("version").get_asString());
908 result.nextCheckAt.SetFromDBDateTime(m_pDS->fv("nextcheck").get_asString());
912 catch (...)
914 CLog::Log(LOGERROR, "{} failed on repo '{}'", __FUNCTION__, id);
916 return result;
919 int CAddonDatabase::SetRepoUpdateData(const std::string& id, const RepoUpdateData& updateData)
923 if (!m_pDB)
924 return false;
925 if (!m_pDS)
926 return false;
928 int retId = -1;
929 std::string sql = PrepareSQL("SELECT * FROM repo WHERE addonID='%s'", id.c_str());
930 m_pDS->query(sql);
932 if (m_pDS->eof())
934 sql = PrepareSQL("INSERT INTO repo (id, addonID, lastcheck, version, nextcheck) "
935 "VALUES (NULL, '%s', '%s', '%s', '%s')",
936 id.c_str(), updateData.lastCheckedAt.GetAsDBDateTime().c_str(),
937 updateData.lastCheckedVersion.asString().c_str(),
938 updateData.nextCheckAt.GetAsDBDateTime().c_str());
939 m_pDS->exec(sql);
940 retId = static_cast<int>(m_pDS->lastinsertid());
942 else
944 retId = m_pDS->fv("id").get_asInt();
945 sql = PrepareSQL(
946 "UPDATE repo SET lastcheck='%s', version='%s', nextcheck='%s' WHERE addonID='%s'",
947 updateData.lastCheckedAt.GetAsDBDateTime().c_str(),
948 updateData.lastCheckedVersion.asString().c_str(),
949 updateData.nextCheckAt.GetAsDBDateTime().c_str(), id.c_str());
950 m_pDS->exec(sql);
953 return retId;
955 catch (...)
957 CLog::Log(LOGERROR, "{} failed on repo '{}'", __FUNCTION__, id);
959 return -1;
962 bool CAddonDatabase::Search(const std::string& search, VECADDONS& addons)
966 if (!m_pDB)
967 return false;
968 if (!m_pDS)
969 return false;
971 std::string strSQL;
972 strSQL = PrepareSQL("SELECT id FROM addons WHERE name LIKE '%%%s%%' OR summary LIKE '%%%s%%' "
973 "OR description LIKE '%%%s%%'", search.c_str(), search.c_str(), search.c_str());
975 CLog::Log(LOGDEBUG, "{} query: {}", __FUNCTION__, strSQL);
977 if (!m_pDS->query(strSQL)) return false;
978 if (m_pDS->num_rows() == 0) return false;
980 while (!m_pDS->eof())
982 AddonPtr addon;
983 GetAddon(m_pDS->fv("id").get_asInt(), addon);
984 if (static_cast<int>(addon->Type()) >= static_cast<int>(AddonType::UNKNOWN) + 1 &&
985 static_cast<int>(addon->Type()) < static_cast<int>(AddonType::SCRAPER_LIBRARY))
986 addons.push_back(addon);
987 m_pDS->next();
989 m_pDS->close();
990 return true;
992 catch (...)
994 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
996 return false;
999 bool CAddonDatabase::DisableAddon(const std::string& addonID, AddonDisabledReason disabledReason)
1003 if (!m_pDB)
1004 return false;
1005 if (!m_pDS)
1006 return false;
1008 const std::string sql =
1009 PrepareSQL("UPDATE installed SET enabled=0, disabledReason=%d WHERE addonID='%s'",
1010 static_cast<int>(disabledReason), addonID.c_str());
1011 m_pDS->exec(sql);
1012 return true;
1014 catch (...)
1016 CLog::Log(LOGERROR, "{} failed on addon '{}'", __FUNCTION__, addonID);
1018 return false;
1021 bool CAddonDatabase::EnableAddon(const std::string& addonID)
1025 if (!m_pDB)
1026 return false;
1027 if (!m_pDS)
1028 return false;
1030 const std::string sql = PrepareSQL(
1031 "UPDATE installed SET enabled=1, disabledReason=0 WHERE addonID='%s'", addonID.c_str());
1032 m_pDS->exec(sql);
1033 return true;
1035 catch (...)
1037 CLog::Log(LOGERROR, "{} failed on addon '{}'", __FUNCTION__, addonID);
1039 return false;
1042 bool CAddonDatabase::GetDisabled(std::map<std::string, AddonDisabledReason>& addons)
1046 if (!m_pDB)
1047 return false;
1048 if (!m_pDS)
1049 return false;
1051 const std::string sql =
1052 PrepareSQL("SELECT addonID, disabledReason FROM installed WHERE enabled=0");
1053 m_pDS->query(sql);
1054 while (!m_pDS->eof())
1056 addons.insert({m_pDS->fv("addonID").get_asString(),
1057 static_cast<AddonDisabledReason>(m_pDS->fv("disabledReason").get_asInt())});
1058 m_pDS->next();
1060 m_pDS->close();
1061 return true;
1063 catch (...)
1065 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
1067 return false;
1070 bool CAddonDatabase::GetAddonUpdateRules(
1071 std::map<std::string, std::vector<AddonUpdateRule>>& rulesMap) const
1075 if (!m_pDB)
1076 return false;
1077 if (!m_pDS)
1078 return false;
1080 std::string sql = PrepareSQL("SELECT * FROM update_rules");
1081 m_pDS->query(sql);
1082 while (!m_pDS->eof())
1084 rulesMap[m_pDS->fv("addonID").get_asString()].emplace_back(
1085 static_cast<AddonUpdateRule>(m_pDS->fv("updateRule").get_asInt()));
1086 m_pDS->next();
1088 m_pDS->close();
1089 return true;
1091 catch (...)
1093 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
1095 return false;
1098 bool CAddonDatabase::AddUpdateRuleForAddon(const std::string& addonID, AddonUpdateRule updateRule)
1102 if (!m_pDB)
1103 return false;
1104 if (!m_pDS)
1105 return false;
1107 std::string sql =
1108 PrepareSQL("INSERT INTO update_rules(id, addonID, updateRule) VALUES(NULL, '%s', %d)",
1109 addonID.c_str(), static_cast<int>(updateRule));
1110 m_pDS->exec(sql);
1111 return true;
1113 catch (...)
1115 CLog::Log(LOGERROR, "{} failed on addon '{}'", __FUNCTION__, addonID);
1117 return false;
1120 bool CAddonDatabase::RemoveAllUpdateRulesForAddon(const std::string& addonID)
1122 return RemoveUpdateRuleForAddon(addonID, AddonUpdateRule::ANY);
1125 bool CAddonDatabase::RemoveUpdateRuleForAddon(const std::string& addonID,
1126 AddonUpdateRule updateRule)
1130 if (!m_pDB)
1131 return false;
1132 if (!m_pDS)
1133 return false;
1135 std::string sql = PrepareSQL("DELETE FROM update_rules WHERE addonID='%s'", addonID.c_str());
1137 if (updateRule != AddonUpdateRule::ANY)
1139 sql += PrepareSQL(" AND updateRule = %d", static_cast<int>(updateRule));
1142 m_pDS->exec(sql);
1143 return true;
1145 catch (...)
1147 CLog::Log(LOGERROR, "{} failed on addon '{}'", __FUNCTION__, addonID);
1149 return false;
1152 bool CAddonDatabase::AddPackage(const std::string& addonID,
1153 const std::string& packageFileName,
1154 const std::string& hash)
1156 std::string sql = PrepareSQL("insert or ignore into package(id, addonID, filename, hash)"
1157 "values(NULL, '%s', '%s', '%s')",
1158 addonID.c_str(), packageFileName.c_str(), hash.c_str());
1159 return ExecuteQuery(sql);
1162 bool CAddonDatabase::GetPackageHash(const std::string& addonID,
1163 const std::string& packageFileName,
1164 std::string& hash)
1166 std::string where = PrepareSQL("addonID='%s' and filename='%s'",
1167 addonID.c_str(), packageFileName.c_str());
1168 hash = GetSingleValue("package", "hash", where);
1169 return !hash.empty();
1172 bool CAddonDatabase::RemovePackage(const std::string& packageFileName)
1174 std::string sql = PrepareSQL("delete from package where filename='%s'", packageFileName.c_str());
1175 return ExecuteQuery(sql);
1178 void CAddonDatabase::OnPostUnInstall(const std::string& addonId)
1180 RemoveAllUpdateRulesForAddon(addonId);
1181 DeleteRepository(addonId);
1183 //! @todo should be done before uninstall to avoid any race conditions
1186 if (!m_pDB)
1187 return;
1188 if (!m_pDS)
1189 return;
1190 m_pDS->exec(PrepareSQL("DELETE FROM installed WHERE addonID='%s'", addonId.c_str()));
1192 catch (...)
1194 CLog::Log(LOGERROR, "{} failed on addon {}", __FUNCTION__, addonId);
1198 void CAddonDatabase::GetInstallData(const AddonInfoPtr& addon)
1202 if (!m_pDB)
1203 return;
1204 if (!m_pDS)
1205 return;
1207 m_pDS->query(PrepareSQL("SELECT addonID, installDate, lastUpdated, lastUsed, "
1208 "origin FROM installed WHERE addonID='%s'",
1209 addon->ID().c_str()));
1210 if (!m_pDS->eof())
1212 CAddonInfoBuilder::SetInstallData(
1213 addon, CDateTime::FromDBDateTime(m_pDS->fv("installDate").get_asString()),
1214 CDateTime::FromDBDateTime(m_pDS->fv("lastUpdated").get_asString()),
1215 CDateTime::FromDBDateTime(m_pDS->fv("lastUsed").get_asString()),
1216 m_pDS->fv("origin").get_asString());
1218 m_pDS->close();
1220 catch (...)
1222 CLog::Log(LOGERROR, "CAddonDatabase::{}: failed", __FUNCTION__);
1226 bool CAddonDatabase::AddInstalledAddon(const std::shared_ptr<CAddonInfo>& addon,
1227 const std::string& origin)
1231 if (!m_pDB)
1232 return false;
1233 if (!m_pDS)
1234 return false;
1236 m_pDS->query(PrepareSQL("SELECT * FROM installed WHERE addonID='%s'", addon->ID().c_str()));
1238 if (m_pDS->eof())
1240 std::string now = CDateTime::GetCurrentDateTime().GetAsDBDateTime();
1242 m_pDS->exec(PrepareSQL("INSERT INTO installed(addonID, enabled, installDate, origin) "
1243 "VALUES('%s', 1, '%s', '%s')",
1244 addon->ID().c_str(), now.c_str(), origin.c_str()));
1247 catch (...)
1249 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
1250 return false;
1253 return true;