1 /*
2  * PluginFactory.cpp
3  *
4  * Copyright (c) 2015 Lukas W <lukaswhl/at/gmail.com>
5  *
6  * This file is part of LMMS - https://lmms.io
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public
19  * License along with this program (see COPYING); if not, write to the
20  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301 USA.
22  *
23  */
24 
25 #include "PluginFactory.h"
26 
27 #include <QtCore/QCoreApplication>
28 #include <QtCore/QDebug>
29 #include <QtCore/QDir>
30 #include <QtCore/QLibrary>
31 
32 #include "ConfigManager.h"
33 
34 #ifdef LMMS_BUILD_WIN32
35 	QStringList nameFilters("*.dll");
36 #else
37 	QStringList nameFilters("lib*.so");
38 #endif
39 
qHash(const QFileInfo & fi)40 qint64 qHash(const QFileInfo& fi)
41 {
42 	return qHash(fi.absoluteFilePath());
43 }
44 
45 std::unique_ptr<PluginFactory> PluginFactory::s_instance;
46 
PluginFactory()47 PluginFactory::PluginFactory()
48 {
49 	// Adds a search path relative to the main executable if the path exists.
50 	auto addRelativeIfExists = [this] (const QString& path) {
51 		QDir dir(qApp->applicationDirPath());
52 		if (!path.isEmpty() && dir.cd(path)) {
53 			QDir::addSearchPath("plugins", dir.absolutePath());
54 		}
55 	};
56 
57 	// We're either running LMMS installed on an Unixoid or we're running a
58 	// portable version like we do on Windows.
59 	// We want to find our plugins in both cases:
60 	//  (a) Installed (Unix):
61 	//      e.g. binary at /usr/bin/lmms - plugin dir at /usr/lib/lmms/
62 	//  (b) Portable:
63 	//      e.g. binary at "C:/Program Files/LMMS/lmms.exe"
64 	//           plugins at "C:/Program Files/LMMS/plugins/"
65 
66 #ifndef LMMS_BUILD_WIN32
67 	addRelativeIfExists("../lib/lmms"); // Installed
68 #endif
69 	addRelativeIfExists("plugins"); // Portable
70 #ifdef PLUGIN_DIR // We may also have received a relative directory via a define
71 	addRelativeIfExists(PLUGIN_DIR);
72 #endif
73 	// Or via an environment variable:
74 	QString env_path;
75 	if (!(env_path = qgetenv("LMMS_PLUGIN_DIR")).isEmpty())
76 		QDir::addSearchPath("plugins", env_path);
77 
78 	QDir::addSearchPath("plugins", ConfigManager::inst()->workingDir() + "plugins");
79 
80 	discoverPlugins();
81 }
82 
~PluginFactory()83 PluginFactory::~PluginFactory()
84 {
85 }
86 
instance()87 PluginFactory* PluginFactory::instance()
88 {
89 	if (s_instance == nullptr)
90 		s_instance.reset(new PluginFactory());
91 
92 	return s_instance.get();
93 }
94 
descriptors() const95 const Plugin::DescriptorList PluginFactory::descriptors() const
96 {
97 	return m_descriptors.values();
98 }
99 
descriptors(Plugin::PluginTypes type) const100 const Plugin::DescriptorList PluginFactory::descriptors(Plugin::PluginTypes type) const
101 {
102 	return m_descriptors.values(type);
103 }
104 
pluginInfos() const105 const PluginFactory::PluginInfoList& PluginFactory::pluginInfos() const
106 {
107 	return m_pluginInfos;
108 }
109 
pluginSupportingExtension(const QString & ext)110 const PluginFactory::PluginInfo PluginFactory::pluginSupportingExtension(const QString& ext)
111 {
112 	return m_pluginByExt.value(ext, PluginInfo());
113 }
114 
pluginInfo(const char * name) const115 const PluginFactory::PluginInfo PluginFactory::pluginInfo(const char* name) const
116 {
117 	for (const PluginInfo& info : m_pluginInfos)
118 	{
119 		if (qstrcmp(info.descriptor->name, name) == 0)
120 			return info;
121 	}
122 	return PluginInfo();
123 }
124 
errorString(QString pluginName) const125 QString PluginFactory::errorString(QString pluginName) const
126 {
127 	static QString notfound = qApp->translate("PluginFactory", "Plugin not found.");
128 	return m_errors.value(pluginName, notfound);
129 }
130 
discoverPlugins()131 void PluginFactory::discoverPlugins()
132 {
133 	DescriptorMap descriptors;
134 	PluginInfoList pluginInfos;
135 	m_pluginByExt.clear();
136 
137 	QSet<QFileInfo> files;
138 	for (const QString& searchPath : QDir::searchPaths("plugins"))
139 	{
140 		files.unite(QDir(searchPath).entryInfoList(nameFilters).toSet());
141 	}
142 
143 	// Cheap dependency handling: zynaddsubfx needs ZynAddSubFxCore. By loading
144 	// all libraries twice we ensure that libZynAddSubFxCore is found.
145 	for (const QFileInfo& file : files)
146 	{
147 		QLibrary(file.absoluteFilePath()).load();
148 	}
149 
150 	for (const QFileInfo& file : files)
151 	{
152 		auto library = std::make_shared<QLibrary>(file.absoluteFilePath());
153 
154 		if (! library->load()) {
155 			m_errors[file.baseName()] = library->errorString();
156 			qWarning("%s", library->errorString().toLocal8Bit().data());
157 			continue;
158 		}
159 		if (library->resolve("lmms_plugin_main") == nullptr) {
160 			continue;
161 		}
162 
163 		QString descriptorName = file.baseName() + "_plugin_descriptor";
164 		if( descriptorName.left(3) == "lib" )
165 		{
166 			descriptorName = descriptorName.mid(3);
167 		}
168 
169 		Plugin::Descriptor* pluginDescriptor = reinterpret_cast<Plugin::Descriptor*>(library->resolve(descriptorName.toUtf8().constData()));
170 		if(pluginDescriptor == nullptr)
171 		{
172 			qWarning() << qApp->translate("PluginFactory", "LMMS plugin %1 does not have a plugin descriptor named %2!").
173 						  arg(file.absoluteFilePath()).arg(descriptorName);
174 			continue;
175 		}
176 
177 		PluginInfo info;
178 		info.file = file;
179 		info.library = library;
180 		info.descriptor = pluginDescriptor;
181 		pluginInfos << info;
182 
183 		for (const QString& ext : QString(info.descriptor->supportedFileTypes).split(','))
184 		{
185 			m_pluginByExt.insert(ext, info);
186 		}
187 
188 		descriptors.insert(info.descriptor->type, info.descriptor);
189 	}
190 
191 	m_pluginInfos = pluginInfos;
192 	m_descriptors = descriptors;
193 }
194 
195 
196 
name() const197 const QString PluginFactory::PluginInfo::name() const
198 {
199 	return descriptor ? descriptor->name : QString();
200 }
201