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.
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"
28 using namespace ADDON
;
30 std::string
CAddonDatabaseSerializer::SerializeMetadata(const CAddonInfo
& addon
)
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
));
74 CJSONVariantWriter::Write(variant
, json
, true);
78 CVariant
CAddonDatabaseSerializer::SerializeExtensions(const CAddonExtensions
& addonType
)
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
));
112 void CAddonDatabaseSerializer::DeserializeMetadata(const std::string
& document
,
113 CAddonInfoBuilderFromDB
& builder
)
116 if (!CJSONVariantParser::Parse(document
, variant
))
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
));
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();
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();
182 CAddonExtensions childExt
;
183 DeserializeExtensions((*child
)["child"], childExt
);
184 std::string id
= (*child
)["id"].asString();
185 addonType
.m_children
.emplace_back(id
, childExt
);
191 CAddonDatabase::CAddonDatabase() = default;
193 CAddonDatabase::~CAddonDatabase() = default;
195 bool CAddonDatabase::Open()
197 return CDatabase::Open();
200 int CAddonDatabase::GetMinSchemaVersion() const
205 int CAddonDatabase::GetSchemaVersion() const
210 void CAddonDatabase::CreateTables()
212 CLog::Log(LOGINFO
, "create addons table");
213 m_pDS
->exec("CREATE TABLE addons ("
214 "id INTEGER PRIMARY KEY,"
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");
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
)
257 m_pDS
->exec("DROP TABLE system");
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");
269 m_pDS
->exec("ALTER TABLE installed ADD origin TEXT NOT NULL DEFAULT ''");
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,"
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)");
289 m_pDS
->exec("ALTER TABLE addons ADD news TEXT NOT NULL DEFAULT ''");
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");
299 m_pDS
->exec("DROP TABLE broken");
303 m_pDS
->exec("ALTER TABLE repo ADD nextcheck TEXT");
307 m_pDS
->exec("UPDATE installed SET origin = addonID WHERE (origin='') AND "
308 "EXISTS (SELECT * FROM repo WHERE repo.addonID = installed.addonID)");
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 "
316 m_pDS
->exec("DROP INDEX IF EXISTS idxBlack");
317 m_pDS
->exec("DROP TABLE blacklist");
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();
328 if (!CJSONVariantParser::Parse(metadata
, variant
))
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();
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
));
373 variant
["extensions"][0] = variantUpdate
;
376 CJSONVariantWriter::Write(variant
, json
, true);
377 m_pDS
->exec(PrepareSQL("UPDATE addons SET metadata='%s' WHERE id=%i", json
.c_str(), id
));
385 void CAddonDatabase::SyncInstalled(const std::set
<std::string
>& ids
,
386 const std::set
<std::string
>& system
,
387 const std::set
<std::string
>& optional
)
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());
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();
418 for (const auto& id
: added
)
422 if (system
.find(id
) != system
.end() || optional
.find(id
) != optional
.end())
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
,
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
,
455 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
456 RollbackTransaction();
460 bool CAddonDatabase::SetLastUpdated(const std::string
& addonId
, const CDateTime
& dateTime
)
469 m_pDS
->exec(PrepareSQL("UPDATE installed SET lastUpdated='%s' WHERE addonID='%s'",
470 dateTime
.GetAsDBDateTime().c_str(), addonId
.c_str()));
475 CLog::Log(LOGERROR
, "{} failed on addon '{}'", __FUNCTION__
, addonId
);
480 bool CAddonDatabase::SetOrigin(const std::string
& addonId
, const std::string
& origin
)
489 m_pDS
->exec(PrepareSQL("UPDATE installed SET origin='%s' WHERE addonID='%s'", origin
.c_str(), addonId
.c_str()));
494 CLog::Log(LOGERROR
, "{} failed on addon '{}'", __FUNCTION__
, addonId
);
499 bool CAddonDatabase::SetLastUsed(const std::string
& addonId
, const CDateTime
& dateTime
)
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());
519 CLog::Log(LOGERROR
, "{} failed on addon '{}'", __FUNCTION__
, addonId
);
524 bool CAddonDatabase::FindByAddonId(const std::string
& addonId
, ADDON::VECADDONS
& result
) const
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 "
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());
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
);
562 addons
.push_back(std::move(addon
));
564 CLog::Log(LOGERROR
, "CAddonDatabase: failed to build {}", addonId
);
568 result
= std::move(addons
);
573 CLog::Log(LOGERROR
, "{} failed on addon {}", __FUNCTION__
, addonId
);
578 bool CAddonDatabase::GetAddon(const std::string
& addonID
,
579 const CAddonVersion
& version
,
580 const std::string
& repoId
,
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());
601 return GetAddon(m_pDS
->fv("id").get_asInt(), addon
);
605 CLog::Log(LOGERROR
, "{} failed on addon {}", __FUNCTION__
, addonID
);
611 bool CAddonDatabase::GetAddon(int id
, AddonPtr
&addon
)
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",
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;
643 CLog::Log(LOGERROR
, "{} failed on addon {}", __FUNCTION__
, id
);
648 bool CAddonDatabase::GetRepositoryContent(VECADDONS
& addons
) const
650 return GetRepositoryContent("", addons
);
653 bool CAddonDatabase::GetRepositoryContent(const std::string
& id
, VECADDONS
& addons
) const
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)");
674 sql
+= PrepareSQL(" AND repo.addonId='%s'", id
.c_str());
677 while (!m_pDS
->eof())
679 repoIds
.emplace_back(m_pDS
->fv("id").get_asString());
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());
692 CLog::Log(LOGDEBUG
, "CAddonDatabase: no valid repository, continuing");
697 CLog::Log(LOGDEBUG
, "CAddonDatabase: no valid repository matching '{}'", id
);
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();
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());
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
);
738 result
.emplace_back(std::move(addon
));
741 CLog::Log(LOGWARNING
, "CAddonDatabase: failed to build {}", addonId
);
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());
755 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
760 void CAddonDatabase::DeleteRepository(const std::string
& id
)
769 int idRepo
= GetRepositoryId(id
);
773 m_pDS
->exec(PrepareSQL("DELETE FROM repo WHERE id=%i", idRepo
));
777 CLog::Log(LOGERROR
, "{} failed on repo '{}'", __FUNCTION__
, id
);
781 void CAddonDatabase::DeleteRepositoryContents(const std::string
& id
)
790 int idRepo
= GetRepositoryId(id
);
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
));
799 CLog::Log(LOGERROR
, "{} failed on repo '{}'", __FUNCTION__
, id
);
803 int CAddonDatabase::GetRepositoryId(const std::string
& addonId
)
810 m_pDS
->query(PrepareSQL("SELECT id FROM repo WHERE addonID='%s'", addonId
.c_str()));
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
)
829 DeleteRepositoryContents(repository
);
830 int idRepo
= GetRepositoryId(repository
);
836 m_pDB
->start_transaction();
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());
851 CLog::Log(LOGERROR
, "{} insert failed on addon '{}'", __FUNCTION__
, addon
->ID());
852 RollbackTransaction();
856 m_pDS
->exec(PrepareSQL("INSERT INTO addonlinkrepo (idRepo, idAddon) VALUES (%i, %i)", idRepo
, idAddon
));
859 m_pDB
->commit_transaction();
864 CLog::Log(LOGERROR
, "{} failed on repo '{}'", __FUNCTION__
, repository
);
865 RollbackTransaction();
870 int CAddonDatabase::GetRepoChecksum(const std::string
& id
, std::string
& checksum
)
879 std::string strSQL
= PrepareSQL("select * from repo where addonID='%s'",id
.c_str());
880 m_pDS
->query(strSQL
);
883 checksum
= m_pDS
->fv("checksum").get_asString();
884 return m_pDS
->fv("id").get_asInt();
889 CLog::Log(LOGERROR
, "{} failed on repo '{}'", __FUNCTION__
, id
);
895 CAddonDatabase::RepoUpdateData
CAddonDatabase::GetRepoUpdateData(const std::string
& id
)
897 RepoUpdateData result
{};
902 std::string strSQL
= PrepareSQL("select * from repo where addonID='%s'",id
.c_str());
903 m_pDS
->query(strSQL
);
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());
914 CLog::Log(LOGERROR
, "{} failed on repo '{}'", __FUNCTION__
, id
);
919 int CAddonDatabase::SetRepoUpdateData(const std::string
& id
, const RepoUpdateData
& updateData
)
929 std::string sql
= PrepareSQL("SELECT * FROM repo WHERE addonID='%s'", id
.c_str());
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());
940 retId
= static_cast<int>(m_pDS
->lastinsertid());
944 retId
= m_pDS
->fv("id").get_asInt();
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());
957 CLog::Log(LOGERROR
, "{} failed on repo '{}'", __FUNCTION__
, id
);
962 bool CAddonDatabase::Search(const std::string
& search
, VECADDONS
& addons
)
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())
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
);
994 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
999 bool CAddonDatabase::DisableAddon(const std::string
& addonID
, AddonDisabledReason disabledReason
)
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());
1016 CLog::Log(LOGERROR
, "{} failed on addon '{}'", __FUNCTION__
, addonID
);
1021 bool CAddonDatabase::EnableAddon(const std::string
& addonID
)
1030 const std::string sql
= PrepareSQL(
1031 "UPDATE installed SET enabled=1, disabledReason=0 WHERE addonID='%s'", addonID
.c_str());
1037 CLog::Log(LOGERROR
, "{} failed on addon '{}'", __FUNCTION__
, addonID
);
1042 bool CAddonDatabase::GetDisabled(std::map
<std::string
, AddonDisabledReason
>& addons
)
1051 const std::string sql
=
1052 PrepareSQL("SELECT addonID, disabledReason FROM installed WHERE enabled=0");
1054 while (!m_pDS
->eof())
1056 addons
.insert({m_pDS
->fv("addonID").get_asString(),
1057 static_cast<AddonDisabledReason
>(m_pDS
->fv("disabledReason").get_asInt())});
1065 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
1070 bool CAddonDatabase::GetAddonUpdateRules(
1071 std::map
<std::string
, std::vector
<AddonUpdateRule
>>& rulesMap
) const
1080 std::string sql
= PrepareSQL("SELECT * FROM update_rules");
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()));
1093 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
1098 bool CAddonDatabase::AddUpdateRuleForAddon(const std::string
& addonID
, AddonUpdateRule updateRule
)
1108 PrepareSQL("INSERT INTO update_rules(id, addonID, updateRule) VALUES(NULL, '%s', %d)",
1109 addonID
.c_str(), static_cast<int>(updateRule
));
1115 CLog::Log(LOGERROR
, "{} failed on addon '{}'", __FUNCTION__
, addonID
);
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
)
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
));
1147 CLog::Log(LOGERROR
, "{} failed on addon '{}'", __FUNCTION__
, addonID
);
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
,
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
1190 m_pDS
->exec(PrepareSQL("DELETE FROM installed WHERE addonID='%s'", addonId
.c_str()));
1194 CLog::Log(LOGERROR
, "{} failed on addon {}", __FUNCTION__
, addonId
);
1198 void CAddonDatabase::GetInstallData(const AddonInfoPtr
& addon
)
1207 m_pDS
->query(PrepareSQL("SELECT addonID, installDate, lastUpdated, lastUsed, "
1208 "origin FROM installed WHERE addonID='%s'",
1209 addon
->ID().c_str()));
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());
1222 CLog::Log(LOGERROR
, "CAddonDatabase::{}: failed", __FUNCTION__
);
1226 bool CAddonDatabase::AddInstalledAddon(const std::shared_ptr
<CAddonInfo
>& addon
,
1227 const std::string
& origin
)
1236 m_pDS
->query(PrepareSQL("SELECT * FROM installed WHERE addonID='%s'", addon
->ID().c_str()));
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()));
1249 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);