1 /*!
2  * \copyright Copyright (c) 2015-2021 Governikus GmbH & Co. KG, Germany
3  */
4 
5 #include "UILoader.h"
6 
7 #include <QLoggingCategory>
8 #include <QPluginLoader>
9 #include <QThread>
10 
11 Q_DECLARE_LOGGING_CATEGORY(gui)
12 
13 using namespace governikus;
14 
15 namespace
16 {
getPrefixUi()17 QString getPrefixUi()
18 {
19 	return QStringLiteral("UIPlugIn");
20 }
21 
22 
23 } // namespace
24 
25 QVector<UIPlugInName> governikus::UILoader::cDefault = UILoader::getInitialDefault();
26 
27 
UILoader()28 UILoader::UILoader()
29 	: mLoadedPlugIns()
30 {
31 }
32 
33 
~UILoader()34 UILoader::~UILoader()
35 {
36 }
37 
38 
getInitialDefault()39 QVector<UIPlugInName> UILoader::getInitialDefault()
40 {
41 	QVector<UIPlugInName> list({UIPlugInName::UIPlugInQml});
42 
43 #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
44 	list << UIPlugInName::UIPlugInWebSocket;
45 #endif
46 
47 	return list;
48 }
49 
50 
isLoaded() const51 bool UILoader::isLoaded() const
52 {
53 	return !mLoadedPlugIns.isEmpty();
54 }
55 
56 
load()57 bool UILoader::load()
58 {
59 	bool any = false;
60 	for (auto entry : qAsConst(cDefault))
61 	{
62 		any = load(entry) || any;
63 	}
64 	return any;
65 }
66 
67 
load(UIPlugInName pUi)68 bool UILoader::load(UIPlugInName pUi)
69 {
70 	Q_ASSERT(QObject::thread() == QThread::currentThread());
71 
72 	if (mLoadedPlugIns.contains(pUi))
73 	{
74 		return true;
75 	}
76 
77 	const auto& name = getEnumName(pUi);
78 	qCDebug(gui) << "Try to load UI plugin:" << name;
79 
80 	const auto& allPlugins = QPluginLoader::staticPlugins();
81 	for (auto& plugin : allPlugins)
82 	{
83 		auto metaData = plugin.metaData();
84 		if (isPlugIn(metaData) && hasName(metaData, name))
85 		{
86 			qCDebug(gui) << "Load plugin:" << metaData;
87 			auto instance = qobject_cast<UIPlugIn*>(plugin.instance());
88 			if (instance)
89 			{
90 				mLoadedPlugIns.insert(pUi, instance);
91 				Q_EMIT fireLoadedPlugin(instance);
92 				return true;
93 			}
94 			else
95 			{
96 				qCWarning(gui) << "Cannot cast to plugin instance:" << plugin.instance();
97 			}
98 		}
99 	}
100 
101 	qCCritical(gui) << "Cannot find UI plugin:" << name;
102 	return false;
103 }
104 
105 
getDefault()106 QStringList UILoader::getDefault()
107 {
108 	QStringList list;
109 	for (auto entry : qAsConst(cDefault))
110 	{
111 		list << getName(entry);
112 	}
113 	return list;
114 }
115 
116 
setDefault(const QStringList & pDefault)117 void UILoader::setDefault(const QStringList& pDefault)
118 {
119 	QVector<UIPlugInName> selectedPlugins;
120 	const auto& availablePlugins = Enum<UIPlugInName>::getList();
121 
122 	for (const auto& parsedUiOption : pDefault)
123 	{
124 		for (auto availablePluginEntry : availablePlugins)
125 		{
126 			if (parsedUiOption.compare(QString(getEnumName(availablePluginEntry)).remove(getPrefixUi()), Qt::CaseInsensitive) == 0)
127 			{
128 				selectedPlugins << availablePluginEntry;
129 			}
130 		}
131 	}
132 
133 	if (!selectedPlugins.isEmpty())
134 	{
135 		cDefault = selectedPlugins;
136 	}
137 }
138 
139 
getLoaded(UIPlugInName pName) const140 UIPlugIn* UILoader::getLoaded(UIPlugInName pName) const
141 {
142 	return mLoadedPlugIns.value(pName);
143 }
144 
145 
shutdown()146 void UILoader::shutdown()
147 {
148 	qCDebug(gui) << "Shutdown UILoader";
149 	const QList<UIPlugInName> keys = mLoadedPlugIns.keys();
150 	for (UIPlugInName key : keys)
151 	{
152 		UIPlugIn* const plugin = mLoadedPlugIns.value(key);
153 
154 		connect(plugin, &QObject::destroyed, this, [this, key] {
155 					qCDebug(gui) << "Shutdown UI:" << key;
156 					mLoadedPlugIns.remove(key);
157 					if (mLoadedPlugIns.isEmpty())
158 					{
159 						Q_EMIT fireShutdownComplete();
160 					}
161 				}, Qt::QueuedConnection);
162 
163 		// Plugins and therefore their members are not auto destructed due to a bug in Qt.
164 		// https://bugreports.qt.io/browse/QTBUG-17458
165 		plugin->deleteLater();
166 	}
167 }
168 
169 
hasName(const QJsonObject & pJson,const QString & pName) const170 bool UILoader::hasName(const QJsonObject& pJson, const QString& pName) const
171 {
172 	return pJson.value(QStringLiteral("className")).toString() == pName;
173 }
174 
175 
getName(UIPlugInName pPlugin)176 QString UILoader::getName(UIPlugInName pPlugin)
177 {
178 	return QString(getEnumName(pPlugin)).remove(getPrefixUi());
179 }
180 
181 
isPlugIn(const QJsonObject & pJson) const182 bool UILoader::isPlugIn(const QJsonObject& pJson) const
183 {
184 	return pJson.value(QStringLiteral("IID")).toString() == QLatin1String("governikus.UIPlugIn");
185 }
186