1 /****************************************************************************************
2  * Copyright (c) 2004-2013 Mark Kretschmann <kretschmann@kde.org>                       *
3  *                                                                                      *
4  * This program is free software; you can redistribute it and/or modify it under        *
5  * the terms of the GNU General Public License as published by the Free Software        *
6  * Foundation; either version 2 of the License, or (at your option) any later           *
7  * version.                                                                             *
8  *                                                                                      *
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
11  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
12  *                                                                                      *
13  * You should have received a copy of the GNU General Public License along with         *
14  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
15  ****************************************************************************************/
16 
17 #define DEBUG_PREFIX "PluginManager"
18 
19 #include "PluginManager.h"
20 
21 #include <core/support/Amarok.h>
22 #include <core/support/Components.h>
23 #include <core/support/Debug.h>
24 #include <core-impl/collections/support/CollectionManager.h>
25 #include <core-impl/storage/StorageManager.h>
26 #include <services/ServiceBase.h>
27 #include <services/ServicePluginManager.h>
28 #include <statsyncing/Controller.h>
29 #include <statsyncing/ProviderFactory.h>
30 #include <storage/StorageFactory.h>
31 
32 #include <KLocalizedString>
33 #include <KMessageBox>
34 #include <KPluginLoader>
35 
36 #include <QGuiApplication>
37 
38 
39 /** Defines the used plugin version number.
40  *
41  *  This must match the desktop files.
42  */
43 const int Plugins::PluginManager::s_pluginFrameworkVersion = 74;
44 Plugins::PluginManager* Plugins::PluginManager::s_instance = nullptr;
45 
46 Plugins::PluginManager*
instance()47 Plugins::PluginManager::instance()
48 {
49     return s_instance ? s_instance : new PluginManager();
50 }
51 
52 void
destroy()53 Plugins::PluginManager::destroy()
54 {
55     if( s_instance )
56     {
57         delete s_instance;
58         s_instance = nullptr;
59     }
60 }
61 
PluginManager(QObject * parent)62 Plugins::PluginManager::PluginManager( QObject *parent )
63     : QObject( parent )
64 {
65     DEBUG_BLOCK
66     setObjectName( "PluginManager" );
67     s_instance = this;
68 
69     PERF_LOG( "Initialising Plugin Manager" )
70     init();
71     PERF_LOG( "Initialised Plugin Manager" )
72 }
73 
~PluginManager()74 Plugins::PluginManager::~PluginManager()
75 {
76     // tell the managers to get rid of their current factories
77     QList<QSharedPointer<Plugins::PluginFactory> > emptyFactories;
78 
79     StatSyncing::Controller *controller = Amarok::Components::statSyncingController();
80     if( controller )
81         controller->setFactories( emptyFactories );
82     ServicePluginManager::instance()->setFactories( emptyFactories );
83     CollectionManager::instance()->setFactories( emptyFactories );
84     StorageManager::instance()->setFactories( emptyFactories );
85 }
86 
87 void
init()88 Plugins::PluginManager::init()
89 {
90     checkPluginEnabledStates();
91 }
92 
93 KPluginInfo::List
plugins(Type type) const94 Plugins::PluginManager::plugins( Type type ) const
95 {
96     KPluginInfo::List infos;
97 
98     for( const auto &pluginInfo : m_pluginsByType.value( type ) )
99     {
100         auto info = KPluginInfo( pluginInfo );
101         info.setConfig( Amarok::config( "Plugins" ) );
102         infos << info;
103     }
104 
105     return infos;
106 }
107 
108 QVector<KPluginMetaData>
enabledPlugins(Plugins::PluginManager::Type type) const109 Plugins::PluginManager::enabledPlugins(Plugins::PluginManager::Type type) const
110 {
111     QVector<KPluginMetaData> enabledList;
112 
113     for( const auto &plugin : m_pluginsByType.value( type ) )
114     {
115         if( isPluginEnabled( plugin ) )
116             enabledList << plugin;
117     }
118 
119     return enabledList;
120 }
121 
122 QList<QSharedPointer<Plugins::PluginFactory> >
factories(Type type) const123 Plugins::PluginManager::factories( Type type ) const
124 {
125     return m_factoriesByType.value( type );
126 }
127 
128 void
checkPluginEnabledStates()129 Plugins::PluginManager::checkPluginEnabledStates()
130 {
131     DEBUG_BLOCK
132 
133     // re-create all the member infos.
134     m_plugins.clear();
135     m_pluginsByType.clear();
136     m_factoriesByType.clear();
137 
138     m_plugins = findPlugins(); // reload all the plugins plus their enabled state
139 
140     if( m_plugins.isEmpty() ) // exit if no plugins are found
141     {
142         if( qobject_cast<QGuiApplication*>( qApp ) )
143         {
144             KMessageBox::error( 0, i18n( "Amarok could not find any plugins. This indicates an installation problem." ) );
145         }
146         else
147         {
148             warning() << "Amarok could not find any plugins. Bailing out.";
149         }
150         // don't use QApplication::exit, as the eventloop may not have started yet
151         std::exit( EXIT_SUCCESS );
152     }
153 
154     // sort the plugin infos by type
155     for( const auto &pluginInfo : m_plugins )
156     {
157         // create the factories and sort them by type
158         auto factory = createFactory( pluginInfo );
159 
160         if( factory )
161         {
162             Type type;
163 
164             if( qobject_cast<StorageFactory*>( factory ) )
165                 type = Storage;
166             else if( qobject_cast<Collections::CollectionFactory*>( factory ) )
167                 type = Collection;
168             else if( qobject_cast<ServiceFactory*>( factory ) )
169                 type = Service;
170             else if( qobject_cast<StatSyncing::ProviderFactory*>( factory ) )
171                 type = Importer;
172             else
173             {
174                 warning() << pluginInfo.name() << "has unknown category";
175                 warning() << pluginInfo.rawData().keys();
176                 continue;
177             }
178 
179             m_pluginsByType[ type ] << pluginInfo;
180 
181             if( isPluginEnabled( pluginInfo ) )
182                 m_factoriesByType[ type ] << factory;
183         }
184         else
185             warning() << pluginInfo.name() << "could not create factory";
186     }
187 
188     // the setFactories functions should:
189     // - filter out factories not useful (e.g. services when setting collections)
190     // - handle the new list of factories, disabling old ones and enabling new ones.
191 
192 
193     PERF_LOG( "Loading storage plugins" )
194     StorageManager::instance()->setFactories( m_factoriesByType.value( Storage ) );
195     PERF_LOG( "Loaded storage plugins" )
196 
197     PERF_LOG( "Loading collection plugins" )
198     CollectionManager::instance()->setFactories( m_factoriesByType.value( Collection ) );
199     PERF_LOG( "Loaded collection plugins" )
200 
201     PERF_LOG( "Loading service plugins" )
202     ServicePluginManager::instance()->setFactories( m_factoriesByType.value( Service ) );
203     PERF_LOG( "Loaded service plugins" )
204 
205     PERF_LOG( "Loading importer plugins" )
206     StatSyncing::Controller *controller = Amarok::Components::statSyncingController();
207     if( controller )
208         controller->setFactories( m_factoriesByType.value( Importer ) );
209     PERF_LOG( "Loaded importer plugins" )
210 
211     // init all new factories
212     // do this after they were added to the sub-manager so that they
213     // have a chance to connect to signals
214     //
215     // we need to init by type and the storages need to go first
216     for( const auto &factory : m_factoriesByType[ Storage ] )
217         factory->init();
218     for( const auto &factory : m_factoriesByType[ Collection ] )
219         factory->init();
220     for( const auto &factory : m_factoriesByType[ Service ] )
221         factory->init();
222     for( const auto &factory : m_factoriesByType[ Importer ] )
223         factory->init();
224 }
225 
226 
227 bool
isPluginEnabled(const KPluginMetaData & plugin) const228 Plugins::PluginManager::isPluginEnabled( const KPluginMetaData &plugin ) const
229 {
230     // mysql storage and collection are vital. They need to be loaded always
231 
232     auto raw = plugin.rawData();
233     int version = raw.value( "X-KDE-Amarok-framework-version" ).toInt();
234     int rank = raw.value( "X-KDE-Amarok-rank" ).toInt();
235 
236     if( version != s_pluginFrameworkVersion )
237     {
238         warning() << "Plugin" << plugin.pluginId() << "has frameworks version" << version
239                   << ". Version" << s_pluginFrameworkVersion << "is required";
240         return false;
241     }
242 
243     if( rank == 0 )
244     {
245         warning() << "Plugin" << plugin.pluginId() << "has rank 0";
246         return false;
247     }
248 
249     auto vital = raw.value( QStringLiteral( "X-KDE-Amarok-vital" ) );
250 
251     if( !vital.isUndefined())
252     {
253         if( vital.toBool() || vital.toString().toLower() == "true" )
254         {
255             debug() << "Plugin" << plugin.pluginId() << "is vital";
256             return true;
257         }
258     }
259 
260     KPluginInfo info = KPluginInfo( plugin );
261     info.setConfig( Amarok::config( "Plugins" ) );
262     info.load();
263 
264     return info.isPluginEnabled();
265 }
266 
267 
268 QSharedPointer<Plugins::PluginFactory>
createFactory(const KPluginMetaData & pluginInfo)269 Plugins::PluginManager::createFactory( const KPluginMetaData &pluginInfo )
270 {
271     debug() << "Creating factory for plugin:" << pluginInfo.pluginId();
272 
273     // check if we already created this factory
274     // note: old factories are not deleted.
275     //   We can't very well just destroy a factory being
276     //   currently used.
277     const QString name = pluginInfo.pluginId();
278 
279     if( m_factoryCreated.contains( name ) )
280         return m_factoryCreated.value( name );
281 
282     QPluginLoader loader( pluginInfo.fileName() );
283     auto pointer = qobject_cast<PluginFactory*>( loader.instance() );
284     auto pluginFactory = QSharedPointer<Plugins::PluginFactory>( pointer );
285 
286     if( !pluginFactory )
287     {
288         warning() << QString( "Failed to get factory '%1' from QPluginLoader: %2" )
289                      .arg( name, loader.errorString() );
290         return QSharedPointer<Plugins::PluginFactory>();
291     }
292 
293     m_factoryCreated[ name ] = pluginFactory;
294     return pluginFactory;
295 }
296 
297 
298 QVector<KPluginMetaData>
findPlugins()299 Plugins::PluginManager::findPlugins()
300 {
301     QVector<KPluginMetaData> plugins;
302     for( const auto &location : QCoreApplication::libraryPaths() )
303         plugins << KPluginLoader::findPlugins( location, [] ( const KPluginMetaData &metadata )
304             { return metadata.serviceTypes().contains( QStringLiteral( "Amarok/Plugin" ) ); } );
305 
306     for( const auto &plugin : plugins )
307     {
308         bool enabled = isPluginEnabled( plugin );
309         debug() << "found plugin:" << plugin.pluginId()
310                 << "enabled:" << enabled;
311     }
312     debug() << plugins.count() << "plugins in total";
313 
314     return plugins;
315 }
316 
317 int
pluginFrameworkVersion()318 Plugins::PluginManager::pluginFrameworkVersion()
319 {
320     return s_pluginFrameworkVersion;
321 }
322 
323