1 /*
2  * Stellarium
3  * Copyright (C) 2006 Fabien Chereau
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
18  */
19 
20 #include <QDebug>
21 #include <QPluginLoader>
22 #include <QSettings>
23 #include <QDir>
24 #include <QRegularExpression>
25 
26 #include "StelModuleMgr.hpp"
27 #include "StelApp.hpp"
28 #include "StelModule.hpp"
29 #include "StelFileMgr.hpp"
30 #include "StelPluginInterface.hpp"
31 #include "StelPropertyMgr.hpp"
32 #include "StelIniParser.hpp"
33 #include "StelLocaleMgr.hpp"
34 #include "StelUtils.hpp"
35 
StelModuleMgr()36 StelModuleMgr::StelModuleMgr() : callingListsToRegenerate(true), pluginDescriptorListLoaded(false)
37 {
38 	qRegisterMetaType<StelModule::StelModuleSelectAction>("StelModule::StelModuleSelectAction");
39 	// Initialize empty call lists for each possible actions
40 	callOrders[StelModule::ActionDraw]=QList<StelModule*>();
41 	callOrders[StelModule::ActionUpdate]=QList<StelModule*>();
42 	callOrders[StelModule::ActionHandleMouseClicks]=QList<StelModule*>();
43 	callOrders[StelModule::ActionHandleMouseMoves]=QList<StelModule*>();
44 	callOrders[StelModule::ActionHandleKeys]=QList<StelModule*>();
45 }
46 
~StelModuleMgr()47 StelModuleMgr::~StelModuleMgr()
48 {
49 }
50 
51 // Regenerate calling lists if necessary
update()52 void StelModuleMgr::update()
53 {
54 	if (callingListsToRegenerate)
55 		generateCallingLists();
56 	callingListsToRegenerate = false;
57 }
58 
59 /*************************************************************************
60  Register a new StelModule to the list
61 *************************************************************************/
registerModule(StelModule * m,bool fgenerateCallingLists)62 void StelModuleMgr::registerModule(StelModule* m, bool fgenerateCallingLists)
63 {
64 	QString name = m->objectName();
65 	if (modules.contains(name))
66 	{
67 		qWarning() << "Module" << name << "is already loaded.";
68 		return;
69 	}
70 	modules.insert(name, m);
71 	m->setParent(this);
72 
73 	//register with StelPropertyMgr
74 	StelApp::getInstance().getStelPropertyManager()->registerObject(m);
75 
76 	if (fgenerateCallingLists)
77 		generateCallingLists();
78 }
79 
80 /*************************************************************************
81  Unregister and delete a StelModule.
82 *************************************************************************/
unloadModule(const QString & moduleID,bool alsoDelete)83 void StelModuleMgr::unloadModule(const QString& moduleID, bool alsoDelete)
84 {
85 	StelModule* m = getModule(moduleID);
86 	if (!m)
87 	{
88 		qWarning() << "Module" << moduleID << "is not loaded.";
89 		return;
90 	}
91 	modules.remove(moduleID);
92 	m->setParent(Q_NULLPTR);
93 	callingListsToRegenerate = true;
94 	if (alsoDelete)
95 	{
96 		m->deinit();
97 		delete m;
98 	}
99 }
100 
101 /*************************************************************************
102  Get the corresponding module or Q_NULLPTR if can't find it.
103 *************************************************************************/
getModule(const QString & moduleID,bool noWarning) const104 StelModule* StelModuleMgr::getModule(const QString& moduleID, bool noWarning) const
105 {
106 	StelModule* module = modules.value(moduleID, Q_NULLPTR);
107 	if (module == Q_NULLPTR)
108 	{
109 		if (noWarning==false)
110 			qWarning() << "Unable to find module called" << moduleID;
111 	}
112 	return module;
113 }
114 
115 
116 /*************************************************************************
117  Load an external plugin
118 *************************************************************************/
loadPlugin(const QString & moduleID)119 StelModule* StelModuleMgr::loadPlugin(const QString& moduleID)
120 {
121 	for (const auto& desc : getPluginsList())
122 	{
123 		if (desc.info.id==moduleID)
124 		{
125 			Q_ASSERT(desc.pluginInterface);
126 			StelModule* sMod = desc.pluginInterface->getStelModule();
127 			qDebug() << "Loaded plugin" << moduleID;
128 			pluginDescriptorList[moduleID].loaded=true;
129 			return sMod;
130 		}
131 	}
132 	qWarning() << "Unable to find plugin called" << moduleID;
133 	return Q_NULLPTR;
134 }
135 
loadExtensions(const QString & moduleID)136 QObjectList StelModuleMgr::loadExtensions(const QString &moduleID)
137 {
138 	for (const auto& desc : getPluginsList())
139 	{
140 		if (desc.info.id==moduleID)
141 		{
142 			Q_ASSERT(desc.pluginInterface);
143 			QObjectList exts = desc.pluginInterface->getExtensionList();
144 			if(!exts.isEmpty())
145 			{
146 				extensions.append(exts);
147 				emit extensionsAdded(exts);
148 				qDebug() << "Loaded"<<exts.size()<<"extensions for"<<moduleID;
149 			}
150 
151 			return exts;
152 		}
153 	}
154 	qWarning() << "Unable to find plugin called" << moduleID;
155 	return QObjectList();
156 }
157 
158 struct StelModuleOrderComparator
159 {
StelModuleOrderComparatorStelModuleOrderComparator160 	StelModuleOrderComparator(StelModule::StelModuleActionName aaction) : action(aaction) {;}
operator ()StelModuleOrderComparator161 	bool operator()(StelModule* x, StelModule* y) {return x->getCallOrder(action)<y->getCallOrder(action);}
162 private:
163 	StelModule::StelModuleActionName action;
164 };
165 
166 
167 // Unload all plugins
unloadAllPlugins()168 void StelModuleMgr::unloadAllPlugins()
169 {
170 	QListIterator<PluginDescriptor> i(getPluginsList());
171 	i.toBack();
172 	while (i.hasPrevious())
173 	{
174 		const PluginDescriptor& d = i.previous();
175 		if (d.loaded==false)
176 			continue;
177 		unloadModule(d.info.id, true);
178 		qDebug() << "Unloaded plugin" << d.info.id;
179 	}
180 	// Call update now to make sure that all references to the now deleted plugins modules
181 	// are removed (fix crashes at application shutdown).
182 	update();
183 }
184 
setPluginLoadAtStartup(const QString & key,bool b)185 void StelModuleMgr::setPluginLoadAtStartup(const QString& key, bool b)
186 {
187 	QSettings* conf = StelApp::getInstance().getSettings();
188 	conf->setValue("plugins_load_at_startup/"+key, b);
189 	if (pluginDescriptorList.contains(key))
190 	{
191 		pluginDescriptorList[key].loadAtStartup=b;
192 	}
193 }
194 
isPluginLoaded(const QString & moduleID)195 bool StelModuleMgr::isPluginLoaded(const QString &moduleID)
196 {
197 	if (pluginDescriptorList.contains(moduleID))
198 		return pluginDescriptorList[moduleID].loaded;
199 	else
200 		return false;
201 }
202 
203 /*************************************************************************
204  Generate properly sorted calling lists for each action (e,g, draw, update)
205  according to modules orders dependencies
206 *************************************************************************/
generateCallingLists()207 void StelModuleMgr::generateCallingLists()
208 {
209 	// For each actions (e.g. "draw", "update", etc..)
210 	for (auto mc = callOrders.begin(); mc != callOrders.end(); ++mc)
211 	{
212 		// Flush previous call orders
213 		mc.value().clear();
214 		// and init them with modules in creation order
215 		for (auto* m : getAllModules())
216 		{
217 			mc.value().push_back(m);
218 		}
219 		std::sort(mc.value().begin(), mc.value().end(), StelModuleOrderComparator(mc.key()));
220 	}
221 }
222 
223 /*************************************************************************
224  Return the list of all the external module found in the modules/ directories
225 *************************************************************************/
getPluginsList()226 QList<StelModuleMgr::PluginDescriptor> StelModuleMgr::getPluginsList()
227 {
228 	if (pluginDescriptorListLoaded)
229 	{
230 		return pluginDescriptorList.values();
231 	}
232 
233 	// First list all static plugins.
234 	// If a dynamic plugin with the same ID exists, it will take precedence on the static one.
235 	for (auto* plugin : QPluginLoader::staticInstances())
236 	{
237 		StelPluginInterface* pluginInterface = qobject_cast<StelPluginInterface*>(plugin);
238 		if (pluginInterface)
239 		{
240 			StelModuleMgr::PluginDescriptor mDesc;
241 			mDesc.info = pluginInterface->getPluginInfo();
242 			mDesc.pluginInterface = pluginInterface;
243 			pluginDescriptorList.insert(mDesc.info.id, mDesc);
244 		}
245 	}
246 
247 	// Then list dynamic libraries from the modules/ directory
248 	QSet<QString> moduleDirs;
249 	moduleDirs = StelFileMgr::listContents("modules",StelFileMgr::Directory);
250 
251 	for (auto dir : moduleDirs)
252 	{
253 		QString moduleFullPath = QString("modules/") + dir + "/lib" + dir;
254 #ifdef Q_OS_WIN
255 		moduleFullPath += ".dll";
256 #else
257 #ifdef Q_OS_MAC
258 		moduleFullPath += ".dylib";
259 #else
260 		moduleFullPath += ".so";
261 #endif
262 #endif
263 		moduleFullPath = StelFileMgr::findFile(moduleFullPath, StelFileMgr::File);
264 		if (moduleFullPath.isEmpty())
265 			continue;
266 
267 		QPluginLoader loader(moduleFullPath);
268 		if (!loader.load())
269 		{
270 			qWarning() << "Couldn't load the dynamic library:" << QDir::toNativeSeparators(moduleFullPath) << ": " << loader.errorString();
271 			qWarning() << "Plugin" << dir << "will not be loaded.";
272 			continue;
273 		}
274 
275 		QObject* obj = loader.instance();
276 		if (!obj)
277 		{
278 			qWarning() << "Couldn't open the dynamic library:" << QDir::toNativeSeparators(moduleFullPath) << ": " << loader.errorString();
279 			qWarning() << "Plugin" << dir << "will not be open.";
280 			continue;
281 		}
282 
283 		StelPluginInterface* pluginInterface = qobject_cast<StelPluginInterface *>(obj);
284 		if (pluginInterface)
285 		{
286 			StelModuleMgr::PluginDescriptor mDesc;
287 			mDesc.info = pluginInterface->getPluginInfo();
288 			mDesc.pluginInterface = pluginInterface;
289 			pluginDescriptorList.insert(mDesc.info.id, mDesc);
290 		}
291 	}
292 
293 	// Load for each plugin if it should be loaded at startup
294 	QSettings* conf = StelApp::getInstance().getSettings();
295 	Q_ASSERT(conf);
296 	conf->beginGroup("plugins_load_at_startup");
297 	for (auto iter = pluginDescriptorList.begin(); iter != pluginDescriptorList.end(); ++iter)
298 	{
299 		bool startByDefault = iter.value().info.startByDefault;
300 		iter->loadAtStartup = conf->value(iter.key(), startByDefault).toBool();
301 		// Save the value in case no such key exists
302 		conf->setValue(iter.key(), iter->loadAtStartup);
303 	}
304 	conf->endGroup();
305 
306 	pluginDescriptorListLoaded = true;
307 	return pluginDescriptorList.values();
308 }
309 
getStandardSupportLinksInfo(QString moduleName,bool furtherInfo)310 QString StelModuleMgr::getStandardSupportLinksInfo(QString moduleName, bool furtherInfo)
311 {
312 	// Regexp to replace {text} with an HTML link.
313 	const QRegularExpression a_rx("[{]([^{]*)[}]");
314 	QString html;
315 	html += "<h3>" + q_("Links") + "</h3>";
316 	html += "<p>" + QString(q_("Support is provided via the Github website.  Be sure to put \"%1\" in the subject when posting.")).arg(moduleName) + "</p>";
317 	html += "<p><ul>";
318 	// TRANSLATORS: The text between braces is the text of an HTML link.
319 	html += "<li>" + q_("If you have a question, you can {get an answer here}.").toHtmlEscaped().replace(a_rx, "<a href=\"https://groups.google.com/forum/#!forum/stellarium\">\\1</a>") + "</li>";
320 	// TRANSLATORS: The text between braces is the text of an HTML link.
321 	html += "<li>" + q_("Bug reports and feature requests can be made {here}.").toHtmlEscaped().replace(a_rx, "<a href=\"https://github.com/Stellarium/stellarium/issues\">\\1</a>") + "</li>";
322 	html += "</ul></p>";
323 	if (furtherInfo)
324 	{
325 		QStringList ver = StelUtils::getApplicationVersion().split(".");
326 		QString URL = QString("<a href=\"http://stellarium.org/doc/%1.%2/\">\\1</a>").arg(ver[0], ver[1]);
327 		// TRANSLATORS: The text between braces is the text of an HTML link.
328 		html += "<p>" + q_("Further information can be found in the {developer documentation}.").toHtmlEscaped().replace(a_rx, URL) + "</p>";
329 	}
330 
331 	return html;
332 }
333