Skip to content

Commit 720e410

Browse files
committed
Implement loading theme and translation extensions.
1 parent 56fb1d8 commit 720e410

File tree

2 files changed

+368
-87
lines changed

2 files changed

+368
-87
lines changed

src/extension.cpp

Lines changed: 212 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,232 @@
11
#include "extension.h"
22

3+
#include <QDirIterator>
34
#include <QFile>
45
#include <QFileInfo>
6+
#include <QJsonDocument>
7+
8+
#include "log.h"
59

610
namespace MOBase
711
{
812

13+
ExtensionMetaData::ExtensionMetaData(QJsonObject const& jsonData) : m_JsonData{jsonData}
14+
{
15+
// read basic fields
16+
m_Identifier = jsonData["identifier"].toString();
17+
m_Type = parseType(jsonData["type"].toString());
18+
m_Name = jsonData["name"].toString();
19+
m_Description = jsonData["description"].toString();
20+
m_Version.parse(jsonData["version"].toString("0.0.0"));
21+
22+
// TODO: name of the key
23+
// translation context
24+
m_TranslationContext = jsonData["translationContext"].toString("");
25+
}
26+
27+
bool ExtensionMetaData::isValid() const
28+
{
29+
return !m_Identifier.isEmpty() && !m_Name.isEmpty() && m_Version.isValid() &&
30+
m_Type != ExtensionType::INVALID;
31+
}
32+
33+
ExtensionType ExtensionMetaData::parseType(QString const& value) const
34+
{
35+
std::map<QString, ExtensionType> stringToTypes{
36+
{"theme", ExtensionType::THEME},
37+
{"translation", ExtensionType::TRANSLATION},
38+
{"plugin", ExtensionType::PLUGIN},
39+
{"game", ExtensionType::GAME}};
40+
41+
auto type = ExtensionType::INVALID;
42+
for (auto& [k, v] : stringToTypes) {
43+
if (k.compare(value, Qt::CaseInsensitive) == 0) {
44+
type = v;
45+
break;
46+
}
47+
}
48+
49+
return type;
50+
}
51+
52+
QString ExtensionMetaData::localized(QString const& value) const
53+
{
54+
// no translation context
55+
if (m_TranslationContext.isEmpty()) {
56+
return value;
57+
}
58+
59+
const auto result = QCoreApplication::translate(m_TranslationContext.toUtf8().data(),
60+
value.toUtf8().data());
61+
return result.isEmpty() ? value : result;
62+
}
63+
964
IExtension::IExtension(std::filesystem::path path, ExtensionMetaData metadata)
1065
: m_Path{std::move(path)}, m_MetaData{std::move(metadata)}
1166
{}
1267

13-
std::vector<QObject*> IExtension::plugins() const
68+
std::unique_ptr<IExtension>
69+
ExtensionFactory::loadExtension(std::filesystem::path directory)
70+
{
71+
const auto metadataPath = directory / METADATA_FILENAME;
72+
73+
if (!exists(metadataPath)) {
74+
log::warn("missing extension metadata in '{}'", directory.native());
75+
return nullptr;
76+
}
77+
78+
// load the meta data
79+
QJsonParseError jsonError;
80+
QJsonDocument jsonMetaData;
81+
{
82+
QFile file(metadataPath);
83+
if (!file.open(QFile::ReadOnly)) {
84+
return {};
85+
}
86+
87+
const auto jsonContent = file.readAll();
88+
jsonMetaData = QJsonDocument::fromJson(jsonContent, &jsonError);
89+
}
90+
91+
if (jsonMetaData.isNull()) {
92+
log::warn("failed to read metadata from '{}': {}", metadataPath.native(),
93+
jsonError.errorString());
94+
return nullptr;
95+
}
96+
97+
return loadExtension(std::move(directory), ExtensionMetaData(jsonMetaData.object()));
98+
}
99+
100+
std::unique_ptr<IExtension>
101+
ExtensionFactory::loadExtension(std::filesystem::path directory,
102+
ExtensionMetaData metadata)
103+
{
104+
if (!metadata.isValid()) {
105+
log::warn("failed to load extension from '{}': invalid metadata",
106+
directory.native());
107+
return nullptr;
108+
}
109+
110+
switch (metadata.type()) {
111+
case ExtensionType::THEME:
112+
return ThemeExtension::loadExtension(std::move(directory), std::move(metadata));
113+
case ExtensionType::TRANSLATION:
114+
return TranslationExtension::loadExtension(std::move(directory),
115+
std::move(metadata));
116+
case ExtensionType::PLUGIN:
117+
case ExtensionType::GAME:
118+
case ExtensionType::INVALID:
119+
default:
120+
log::warn("failed to load extension from '{}': invalid type", directory.native());
121+
return nullptr;
122+
}
123+
}
124+
125+
ThemeExtension::ThemeExtension(std::filesystem::path path, ExtensionMetaData metadata,
126+
std::vector<std::shared_ptr<const Theme>> themes)
127+
: IExtension{std::move(path), std::move(metadata)}, m_Themes{std::move(themes)}
128+
{}
129+
130+
std::unique_ptr<ThemeExtension>
131+
ThemeExtension::loadExtension(std::filesystem::path path, ExtensionMetaData metadata)
14132
{
15-
if (!m_Loaded) {
16-
m_Plugins = fetchPlugins();
17-
m_Loaded = true;
133+
std::vector<std::shared_ptr<const Theme>> themes;
134+
const auto& jsonThemes = metadata.json()["themes"].toObject();
135+
for (auto it = jsonThemes.begin(); it != jsonThemes.end(); ++it) {
136+
const auto theme = parseTheme(path, it.key(), it.value().toObject());
137+
if (theme) {
138+
themes.push_back(theme);
139+
} else {
140+
log::warn("failed to parse theme '{}' from '{}'", it.key(), path.native());
141+
}
142+
}
143+
144+
if (themes.empty()) {
145+
log::error("failed to parse themes from '{}'", path.native());
146+
return nullptr;
18147
}
19-
return m_Plugins;
148+
149+
return std::unique_ptr<ThemeExtension>{
150+
new ThemeExtension(path, metadata, std::move(themes))};
20151
}
21152

22-
std::vector<QObject*> IExtension::loadPlugins()
153+
std::shared_ptr<const Theme>
154+
ThemeExtension::parseTheme(std::filesystem::path const& extensionFolder,
155+
const QString& identifier, const QJsonObject& jsonTheme)
23156
{
24-
// loadPlugins() is just exposed to pre-load plugins
25-
return plugins();
157+
const auto name = jsonTheme["name"].toString();
158+
const auto filepath =
159+
extensionFolder / jsonTheme["path"].toString().toUtf8().toStdString();
160+
161+
if (name.isEmpty() || !is_regular_file(filepath)) {
162+
return nullptr;
163+
}
164+
165+
return std::make_shared<Theme>(identifier.toStdString(), name.toStdString(),
166+
filepath);
167+
}
168+
169+
TranslationExtension::TranslationExtension(
170+
std::filesystem::path path, ExtensionMetaData metadata,
171+
std::vector<std::shared_ptr<const Translation>> translations)
172+
: IExtension{std::move(path), std::move(metadata)},
173+
m_Translations(std::move(translations))
174+
{}
175+
176+
std::unique_ptr<TranslationExtension>
177+
TranslationExtension::loadExtension(std::filesystem::path path,
178+
ExtensionMetaData metadata)
179+
{
180+
std::vector<std::shared_ptr<const Translation>> translations;
181+
const auto& jsonTranslations = metadata.json()["translations"].toObject();
182+
for (auto it = jsonTranslations.begin(); it != jsonTranslations.end(); ++it) {
183+
const auto theme = parseTranslation(path, it.key(), it.value().toObject());
184+
if (theme) {
185+
translations.push_back(theme);
186+
} else {
187+
log::warn("failed to parse translation '{}' from '{}'", it.key(), path.native());
188+
}
189+
}
190+
191+
if (translations.empty()) {
192+
log::error("failed to parse translations from '{}'", path.native());
193+
return nullptr;
194+
}
195+
196+
return std::unique_ptr<TranslationExtension>{
197+
new TranslationExtension(path, metadata, std::move(translations))};
198+
}
199+
200+
#pragma optimize("", off)
201+
std::shared_ptr<const Translation>
202+
TranslationExtension::parseTranslation(std::filesystem::path const& extensionFolder,
203+
const QString& identifier,
204+
const QJsonObject& jsonTranslation)
205+
{
206+
const auto name = jsonTranslation["name"].toString();
207+
const auto jsonGlobFiles = jsonTranslation["files"].toVariant().toStringList();
208+
209+
std::vector<std::filesystem::path> qm_files;
210+
for (const auto& globFile : jsonGlobFiles) {
211+
// use a QFileInfo to extract the name (glob) and the directory - we currently do
212+
// not handle recursive glob (**)
213+
QFileInfo globFileInfo(extensionFolder, globFile);
214+
215+
QDirIterator dirIterator(globFileInfo.absolutePath(), {globFileInfo.fileName()},
216+
QDir::Files);
217+
while (dirIterator.hasNext()) {
218+
dirIterator.next();
219+
qm_files.push_back(dirIterator.fileInfo().filesystemAbsoluteFilePath());
220+
}
221+
}
222+
223+
if (name.isEmpty() || qm_files.empty()) {
224+
return nullptr;
225+
}
226+
227+
return std::make_shared<Translation>(identifier.toStdString(), name.toStdString(),
228+
std::move(qm_files));
26229
}
230+
#pragma optimize("", on)
27231

28232
} // namespace MOBase

0 commit comments

Comments
 (0)