1 #include "pluginmanager.h"
2 #include <QObject>
3 #include <QtScript/QtScript>
4 #include <vcg/complex/algorithms/create/platonic.h>
5 
6 #include "scriptinterface.h"
7 #include "mlexception.h"
8 
9 
DLLExtension()10 static QString DLLExtension() {
11 #if defined(Q_OS_WIN)
12   return QString("dll");
13 #elif defined(Q_OS_MAC)
14   return QString("dylib");
15 #else
16   return QString("so");
17 #endif
18   assert(0 && "Unknown Operative System. Please Define the appropriate dynamic library extension");
19   return QString();
20 }
21 
DLLFileNamePreamble()22 static QString DLLFileNamePreamble() {
23 #if defined(Q_OS_WIN)
24   return QString("");
25 #elif defined(Q_OS_MAC)
26   return QString("lib");
27 #else
28   return QString("lib");
29 #endif
30   assert(0 && "Unknown Operative System. Please Define the appropriate dynamic library preamble");
31   return QString();
32 }
33 
PluginManager()34 PluginManager::PluginManager()
35 :/*currentDocInterface(NULL),*/scriptplugcode()
36 {
37   //pluginsDir=QDir(getPluginDirPath());
38   // without adding the correct library path in the mac the loading of jpg (done via qt plugins) fails
39   //qApp->addLibraryPath(getPluginDirPath());
40   //qApp->addLibraryPath(getBaseDirPath());
41 }
42 
~PluginManager()43 PluginManager::~PluginManager()
44 {
45 	meshIOPlug.clear();
46 	meshFilterPlug.clear();
47 	meshRenderPlug.clear();
48 	meshDecoratePlug.clear();
49 	for (MeshCommonInterface* plugin : ownerPlug)
50 		delete plugin;
51 	ownerPlug.clear();
52 
53 	for (int ii = 0; ii < meshEditInterfacePlug.size(); ++ii)
54 		delete meshEditInterfacePlug[ii];
55 	meshEditInterfacePlug.clear();
56 
57 	for (int ii = 0; ii < meshlabXMLFilterPlug.size(); ++ii)
58 		delete meshlabXMLFilterPlug[ii];
59 	meshlabXMLFilterPlug.clear();
60 
61 	for (int ii = 0; ii < xmlpluginfo.size(); ++ii)
62 		MLXMLPluginInfo::destroyXMLPluginInfo(xmlpluginfo[ii]);
63 	xmlpluginfo.clear();
64 }
65 
66 
67 
loadPlugins(RichParameterSet & defaultGlobal)68 void PluginManager::loadPlugins(RichParameterSet& defaultGlobal)
69 {
70 	pluginsDir = QDir(getDefaultPluginDirPath());
71 	// without adding the correct library path in the mac the loading of jpg (done via qt plugins) fails
72 	qApp->addLibraryPath(getDefaultPluginDirPath());
73 	qApp->addLibraryPath(getBaseDirPath());
74 	QStringList pluginfilters;
75 
76 	pluginfilters << QString("*." + DLLExtension());
77 	pluginfilters << "*.xml";
78 
79 	//only the file with extension pluginfilters will be listed by function entryList()
80 	pluginsDir.setNameFilters(pluginfilters);
81 
82 	qDebug("Current Plugins Dir is: %s ", qUtf8Printable(pluginsDir.absolutePath()));
83 	scriptplugcode = "";
84 	ScriptAdapterGenerator gen;
85 	scriptplugcode += gen.mergeOptParamsCodeGenerator() + "\n";
86 	scriptplugcode += MLXMLUtilityFunctions::pluginsNameSpace() + " = { };\n";
87 	foreach(QString fileName, pluginsDir.entryList(QDir::Files))
88 	{
89 		//      qDebug() << fileName;
90 		QString absfilepath = pluginsDir.absoluteFilePath(fileName);
91 		QFileInfo fin(absfilepath);
92 		if (fin.suffix() == "xml")
93 		{
94 			try
95 			{
96 				MLXMLPluginInfo* pluginfo = loadXMLPlugin(fileName);
97                 if (pluginfo == NULL)
98                 {
99                     QString err;
100                     err = "WARNING! " + fileName + " xml-based plugin was not found!";
101                     throw MLException(err);
102                 }
103 //				//WARNING!!!! Why this piece of code is not inside the loadXMLPlugin function?!?!?!?!?!?!
104 //				//Because one day, hopefully, the RichParameterSet class (the old parameters system) will disappear and we don't want to spread it inside the XML functions
105 
106 //				if (pluginfo != NULL)
107 //				{
108 //					QString pluginprogname = pluginfo->pluginScriptName();
109 //					foreach(QString filtername, pluginfo->filterNames())
110 //					{
111 //						QString filterprogname = pluginfo->filterAttribute(filtername, MLXMLElNames::filterScriptFunctName);
112 //						foreach(MLXMLPluginInfo::XMLMap params, pluginfo->filterParametersExtendedInfo(filtername))
113 //						{
114 //							if (params[MLXMLElNames::paramIsPersistent] == QString("true"))
115 //							{
116 //                                //RichParameter* rp = NULL;
117 //								QString completepreamble = MLXMLUtilityFunctions::completeFilterProgrammingName(MLXMLUtilityFunctions::pluginsNameSpace(), pluginprogname, filterprogname);
118 //								/*RichParameterAdapter::create(completepreamble,params, &rp);
119 //								if (rp != NULL)
120 //								defaultGlobal.addParam(rp);*/
121 //							}
122 //						}
123 //					}
124 //				}
125 			}
126 			catch (MeshLabXMLParsingException& e)
127 			{
128 				qDebug() << e.what();
129 			}
130     }
131 		else
132 		{
133 			QPluginLoader loader(absfilepath);
134 			QObject *plugin = loader.instance();
135 			if (plugin)
136 			{
137 				pluginsLoaded.push_back(fileName);
138 				MeshCommonInterface *iCommon = nullptr;
139 				MeshFilterInterface *iFilter = qobject_cast<MeshFilterInterface *>(plugin);
140 				if (iFilter)
141 				{
142 					iCommon = iFilter;
143 					meshFilterPlug.push_back(iFilter);
144 					foreach(QAction *filterAction, iFilter->actions())
145 					{
146 						filterAction->setData(QVariant(fileName));
147 						actionFilterMap.insert(filterAction->text(), filterAction);
148 						stringFilterMap.insert(filterAction->text(), iFilter);
149 						iFilter->initGlobalParameterSet(filterAction, defaultGlobal);
150                         if(iFilter->getClass(filterAction)==MeshFilterInterface::Generic)
151                           throw MLException("Missing class for "        +fileName+filterAction->text());
152                         if(iFilter->getRequirements(filterAction) == int(MeshModel::MM_UNKNOWN))
153                           throw MLException("Missing requirements for " +fileName+filterAction->text());
154                         if(iFilter->getPreConditions(filterAction) == int(MeshModel::MM_UNKNOWN))
155                           throw MLException("Missing preconditions for "+fileName+filterAction->text());
156                         if(iFilter->postCondition(filterAction) == int(MeshModel::MM_UNKNOWN ))
157                           throw MLException("Missing postcondition for "+fileName+filterAction->text());
158                         if(iFilter->filterArity(filterAction) == MeshFilterInterface::UNKNOWN_ARITY )
159                           throw MLException("Missing Arity for "        +fileName+filterAction->text());
160 					}
161 				}
162 				MeshIOInterface *iIO = qobject_cast<MeshIOInterface *>(plugin);
163 				if (iIO)
164 				{
165 					iCommon = iIO;
166 					meshIOPlug.push_back(iIO);
167 					iIO->initGlobalParameterSet(NULL, defaultGlobal);
168 				}
169 
170 				MeshDecorateInterface *iDecorator = qobject_cast<MeshDecorateInterface *>(plugin);
171 				if (iDecorator)
172 				{
173 					iCommon = iDecorator;
174 					meshDecoratePlug.push_back(iDecorator);
175 					foreach(QAction *decoratorAction, iDecorator->actions())
176 					{
177 						decoratorActionList.push_back(decoratorAction);
178 						iDecorator->initGlobalParameterSet(decoratorAction, defaultGlobal);
179 					}
180 				}
181 
182 				MeshRenderInterface *iRender = qobject_cast<MeshRenderInterface *>(plugin);
183 				if (iRender)
184 				{
185 					iCommon = iRender;
186 					meshRenderPlug.push_back(iRender);
187 				}
188 
189 				MeshEditInterfaceFactory *iEditFactory = qobject_cast<MeshEditInterfaceFactory *>(plugin);
190 				if (iEditFactory)
191 				{
192 					meshEditInterfacePlug.push_back(iEditFactory);
193 					foreach(QAction* editAction, iEditFactory->actions())
194 						editActionList.push_back(editAction);
195 				}
196 				else if (iCommon)
197 				{
198 					ownerPlug.push_back(iCommon);
199 				} else {
200 					// qDebug("Plugin %s was loaded, but could not be casted to any known type.", qUtf8Printable(fileName));
201 				}
202 			}
203 			else
204 				qDebug() << loader.errorString();
205 		}
206 	}
207 	knownIOFormats();
208 }
209 
210 // Search among all the decorator plugins the one that contains a decoration with the given name
getDecoratorInterfaceByName(const QString & name)211 MeshDecorateInterface *PluginManager::getDecoratorInterfaceByName(const QString& name)
212 {
213   foreach(MeshDecorateInterface *tt, this->meshDecoratePlugins())
214   {
215     foreach( QAction *ac, tt->actions())
216       if( name == tt->decorationName(ac) ) return tt;
217   }
218   assert(0);
219   return 0;
220 }
221 
222 /*
223 This function create a map from filtername to dummy RichParameterSet.
224 containing for each filtername the set of parameter that it uses.
225 */
generateFilterParameterMap()226 QMap<QString, RichParameterSet> PluginManager::generateFilterParameterMap()
227 {
228 	QMap<QString, RichParameterSet> FPM;
229 	MeshDocument md;
230 	MeshModel* mm = md.addNewMesh("", "dummy", true);
231 	vcg::tri::Tetrahedron<CMeshO>(mm->cm);
232 	mm->updateDataMask(MeshModel::MM_ALL);
233 	QMap<QString, QAction*>::iterator ai;
234 	for (ai = this->actionFilterMap.begin(); ai != this->actionFilterMap.end(); ++ai)
235 	{
236 		QString filterName = ai.key();//  ->filterName();
237 									  //QAction act(filterName,NULL);
238 		RichParameterSet rp;
239 		stringFilterMap[filterName]->initParameterSet(ai.value(), md, rp);
240 		FPM[filterName] = rp;
241 	}
242 	return FPM;
243 }
244 
osDependentFileBaseName(const QString & plname)245 QString PluginManager::osDependentFileBaseName(const QString &plname)
246 {
247 	return (DLLFileNamePreamble() + plname + "." + DLLExtension());
248 }
249 
getBaseDirPath()250 QString PluginManager::getBaseDirPath()
251 {
252 	QDir baseDir(qApp->applicationDirPath());
253 
254 #if defined(Q_OS_WIN)
255 	// Windows:
256 	// during development with visual studio binary could be in the debug/release subdir.
257 	// once deployed plugins dir is in the application directory, so
258 	if (baseDir.dirName() == "debug" || baseDir.dirName() == "release")		baseDir.cdUp();
259 #endif
260 
261 #if defined(Q_OS_MAC)
262   // Mac: during developmentwith xcode  and well deployed the binary is well buried.
263   for(int i=0;i<6;++i){
264     if(baseDir.exists("plugins")) break;
265     baseDir.cdUp();
266   }
267   qDebug("The base dir is %s", qUtf8Printable(baseDir.absolutePath()));
268 #endif
269   return baseDir.absolutePath();
270 }
271 
getDefaultPluginDirPath()272 QString PluginManager::getDefaultPluginDirPath()
273 {
274 	QDir pluginsDir(getBaseDirPath());
275 #if defined(Q_OS_WIN)
276 	QString d = pluginsDir.dirName();
277 	QString dLower = d.toLower();
278 	if (dLower == "release" || dLower == "relwithdebinfo" || dLower == "debug" ||
279 		dLower == "minsizerel") {
280 		// This is a configuration directory for MS Visual Studio.
281 		pluginsDir.cdUp();
282 	} else {
283 		d.clear();
284 	}
285 #endif
286 	if (pluginsDir.exists("plugins")) {
287 		pluginsDir.cd("plugins");
288 
289 #if defined(Q_OS_WIN)
290 		// Re-apply the configuration dir, if any.
291 		if (!d.isEmpty() && pluginsDir.exists(d)) {
292 			pluginsDir.cd(d);
293 		}
294 #endif
295 
296 		return pluginsDir.absolutePath();
297 	}
298 #if !defined(Q_OS_MAC) && !defined(Q_OS_WIN)
299 	if (pluginsDir.dirName() == "bin") {
300 		pluginsDir.cdUp();
301 		pluginsDir.cd("lib");
302 		pluginsDir.cd("meshlab");
303 		if (pluginsDir.exists("plugins")) {
304 			pluginsDir.cd("plugins");
305 			return pluginsDir.absolutePath();
306 		}
307 	}
308 #endif
309 	//QMessageBox::warning(0,"Meshlab Initialization","Serious error. Unable to find the plugins directory.");
310 	qDebug("Meshlab Initialization: Serious error. Unable to find the plugins directory.");
311 	return {};
312 }
313 
314 
315 
knownIOFormats()316 void PluginManager::knownIOFormats()
317 {
318 	for (int inpOut = 0; inpOut < 2; ++inpOut)
319 	{
320 		QStringList* formatFilters = NULL;
321 		QString allKnownFormatsFilter = QObject::tr("All known formats (");
322 		for (QVector<MeshIOInterface*>::iterator itIOPlugin = meshIOPlug.begin(); itIOPlugin != meshIOPlug.end(); ++itIOPlugin)
323 		{
324 			MeshIOInterface* pMeshIOPlugin = *itIOPlugin;
325 			QList<MeshIOInterface::Format> format;
326 			QMap<QString, MeshIOInterface*>* map = NULL;
327 			if (inpOut == int(IMPORT))
328 			{
329 				map = &allKnowInputFormats;
330 				formatFilters = &inpFilters;
331 				format = pMeshIOPlugin->importFormats();
332 			}
333 			else
334 			{
335 				map = &allKnowOutputFormats;
336 				formatFilters = &outFilters;
337 				format = pMeshIOPlugin->exportFormats();
338 			}
339 			for (QList<MeshIOInterface::Format>::iterator itf = format.begin(); itf != format.end(); ++itf)
340 			{
341 				MeshIOInterface::Format currentFormat = *itf;
342 
343 				QString currentFilterEntry = currentFormat.description + " (";
344 
345 				//a particular file format could be associated with more than one file extension
346 				QStringListIterator itExtension(currentFormat.extensions);
347 				while (itExtension.hasNext())
348 				{
349 					QString currentExtension = itExtension.next().toLower();
350 					if (!map->contains(currentExtension))
351 					{
352 						map->insert(currentExtension, pMeshIOPlugin);
353 						allKnownFormatsFilter.append(QObject::tr(" *."));
354 						allKnownFormatsFilter.append(currentExtension);
355 					}
356 					currentFilterEntry.append(QObject::tr(" *."));
357 					currentFilterEntry.append(currentExtension);
358 				}
359 				currentFilterEntry.append(')');
360 				formatFilters->append(currentFilterEntry);
361 			}
362 
363 		}
364 		allKnownFormatsFilter.append(')');
365 		if (formatFilters != NULL)
366 			formatFilters->push_front(allKnownFormatsFilter);
367 	}
368 }
369 
370 //void PluginManager::updateDocumentScriptBindings(MeshDocument& doc )
371 //{
372 //	//WARNING!
373 //	//all the currentDocInterface created will be destroyed by QT when the MeshDocument destructor has been called
374 //	currentDocInterface = new MeshDocumentSI(&doc);
375 //	QScriptValue val = env.newQObject(currentDocInterface);
376 //	env.globalObject().setProperty(ScriptAdapterGenerator::meshDocVarName(),val);
377 //}
378 
pluginsCode() const379 QString PluginManager::pluginsCode() const
380 {
381   return scriptplugcode;
382 }
383 
loadXMLPlugin(const QString & fileName)384 MLXMLPluginInfo* PluginManager::loadXMLPlugin(const QString& fileName)
385 {
386 	ScriptAdapterGenerator gen;
387 	QString absfilepath = pluginsDir.absoluteFilePath(fileName);
388 	QFileInfo fin(absfilepath);
389 	if (fin.suffix() == "xml")
390 	{
391 
392 		QString dllfile = osDependentFileBaseName(fin.completeBaseName());
393 
394 		MeshLabXMLFilterContainer fc;
395 		//fc.filterInterface = NULL;
396 		XMLMessageHandler xmlErr;
397 		MLXMLPluginInfo* pluginfo = MLXMLPluginInfo::createXMLPluginInfo(absfilepath, MLXMLUtilityFunctions::xmlSchemaFile(), xmlErr);
398 		if (pluginfo != NULL)
399 		{
400 			xmlpluginfo << pluginfo;
401 			fc.xmlInfo = xmlpluginfo[xmlpluginfo.size() - 1];
402 			//      QStringList fn = fc.xmlInfo->filterNames();
403 			QObject* par = NULL;
404 			if (pluginsDir.exists(dllfile))
405 			{
406 				QPluginLoader loader(fin.absoluteDir().absolutePath() + "/" + dllfile);
407 				QObject* plugin = loader.instance();
408 				MeshLabFilterInterface* iXMLfilter = qobject_cast<MeshLabFilterInterface *>(plugin);
409 				if (iXMLfilter != NULL)
410 				{
411 					meshlabXMLFilterPlug << iXMLfilter;
412 					fc.filterInterface = meshlabXMLFilterPlug[meshlabXMLFilterPlug.size() - 1];
413 					par = plugin;
414 				}
415 			}
416 			else
417 			{
418 				// we have loaded an xml without the corresponding dll. Let's check that it is a pure javascript plugin
419 				bool foundANonJavaScriptFilter = false;
420 				foreach(QString filterName, pluginfo->filterNames())
421 				{
422 					if (pluginfo->filterElement(filterName, MLXMLElNames::filterJSCodeTag).isEmpty())
423 						foundANonJavaScriptFilter = true;
424 				}
425 				if (foundANonJavaScriptFilter)
426 				{
427 					throw(MeshLabXMLParsingException("We are trying to load a xml file that does not correspond to any dll or javascript code; please delete all the spurious xml files"));
428 				}
429 				par = new QObject();
430 			}
431 			QString pname = pluginfo->pluginScriptName();
432 			if (pname != "")
433 			{
434 				QString plugnamespace = MLXMLUtilityFunctions::pluginsNameSpace() + "." + pname;
435 				//pluginnamespaces << plugnamespace;
436 				scriptplugcode += plugnamespace + " = { };\n";
437 				QStringList filters = pluginfo->filterNames();
438 				foreach(QString filter, filters)
439 				{
440 					MLXMLPluginInfo::XMLMapList paramsinfo = pluginfo->filterParametersExtendedInfo(filter);
441 					QString completename = plugnamespace;
442 					fc.act = new QAction(filter, par);
443 					fc.act->setData(QVariant(dllfile));
444 					stringXMLFilterMap.insert(filter, fc);
445 					QString filterFunction = pluginfo->filterScriptCode(filter);
446 					if (filterFunction == "")
447 						filterFunction = gen.funCodeGenerator(filter, *pluginfo);
448 					else
449 						filterFunction = "{" + filterFunction + "};";
450 					QString jname = pluginfo->filterAttribute(filter, MLXMLElNames::filterScriptFunctName);
451 					completename += "." + jname;
452 					//filterscriptnames << completename;
453 					scriptplugcode += completename + " = " + filterFunction + "\n";
454 					completename += ".";
455 					//for (MLXMLPluginInfo::XMLMap& paraminfo : paramsinfo)
456 					//{
457 					//	if (paraminfo[MLXMLElNames::paramIsPersistent] == "true")
458 					//	{
459 					//		QString fullvarname = completename + paraminfo[MLXMLElNames::paramName];
460 					//		//scriptplugcode += namespaceVariableAssignment(fullvarname, paraminfo[MLXMLElNames::paramDefExpr]);
461 					//		paraminfo[MLXMLElNames::paramDefExpr] = fullvarname;
462 					//	}
463 					//}
464 
465 					completename += "(" + gen.parNames(filter, *pluginfo) + ")";
466 					LibraryElementInfo li;
467 					li.completename = completename;
468 					li.help = pluginfo->filterHelp(filter);
469 					libinfolist << li;
470 				}
471 			}
472 			return pluginfo;
473 		}
474 		else
475 		{
476 			QString err = xmlErr.statusMessage();
477 			qDebug("Error in XMLFile: %s - line: %d, column: %d - %s", qUtf8Printable(fileName), xmlErr.line(), xmlErr.column(), qUtf8Printable(err));
478 		}
479 	}
480 	return nullptr;
481 }
482 
483 //MLXMLPluginInfo* PluginManager::getXMLPluginInfo( const QString& plugname )
484 //{
485 //	for(int ii = 0;ii < xmlpluginfo.size();++ii)
486 //		if (xmlpluginfo[ii]->pluginFilePath() != plugname)
487 //			return xmlpluginfo[ii];
488 //	return NULL;
489 //}
490 
deleteXMLPlugin(const QString & plugscriptname)491 void PluginManager::deleteXMLPlugin(const QString& plugscriptname)
492 {
493 	bool found = false;
494 	int ii = 0;
495 	while ((ii < xmlpluginfo.size()) && !found)
496 	{
497 		if (xmlpluginfo[ii]->pluginScriptName() == plugscriptname)
498 			found = true;
499 		else
500 			++ii;
501 	}
502 	if (found)
503 	{
504 		QStringList removefilters;
505 		QSet<MeshLabFilterInterface*> tobedeleted;
506 		for (QMap<QString, MeshLabXMLFilterContainer>::iterator it = stringXMLFilterMap.begin(); it != stringXMLFilterMap.end();)
507 		{
508 			if (xmlpluginfo[ii] == it.value().xmlInfo)
509 			{
510 				QString rem = it.key();
511 				if (it.value().filterInterface != NULL)
512 					tobedeleted.insert(it.value().filterInterface);
513 				++it;
514 				stringXMLFilterMap.remove(rem);
515 			}
516 			else
517 				++it;
518 		}
519 		MLXMLPluginInfo* tmp = xmlpluginfo[ii];
520 		xmlpluginfo.remove(ii);
521 		MLXMLPluginInfo::destroyXMLPluginInfo(tmp);
522 		for (QSet<MeshLabFilterInterface*>::iterator it = tobedeleted.begin(); it != tobedeleted.end(); ++it)
523 		{
524 			int ii = meshlabXMLfilterPlugins().indexOf(*it);
525 			MeshLabFilterInterface* fi = meshlabXMLfilterPlugins()[ii];
526 			meshlabXMLfilterPlugins().remove(ii);
527 			delete fi;
528 		}
529 	}
530 }
531 
osIndependentPluginName(const QString & plname)532 QString PluginManager::osIndependentPluginName(const QString& plname)
533 {
534 	QFileInfo fi(plname);
535 	QString res = fi.baseName();
536 	QString pref = DLLFileNamePreamble();
537 	return res.remove(0, pref.size());
538 }
539 
540