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