File indexing completed on 2024-04-21 04:47:53

0001 /****************************************************************************************
0002  * Copyright (c) 2004-2013 Mark Kretschmann <kretschmann@kde.org>                       *
0003  *                                                                                      *
0004  * This program is free software; you can redistribute it and/or modify it under        *
0005  * the terms of the GNU General Public License as published by the Free Software        *
0006  * Foundation; either version 2 of the License, or (at your option) any later           *
0007  * version.                                                                             *
0008  *                                                                                      *
0009  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0010  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0011  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0012  *                                                                                      *
0013  * You should have received a copy of the GNU General Public License along with         *
0014  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0015  ****************************************************************************************/
0016 
0017 #define DEBUG_PREFIX "PluginManager"
0018 
0019 #include "PluginManager.h"
0020 
0021 #include <core/support/Amarok.h>
0022 #include <core/support/Components.h>
0023 #include <core/support/Debug.h>
0024 #include <core-impl/collections/support/CollectionManager.h>
0025 #include <core-impl/storage/StorageManager.h>
0026 #include <services/ServiceBase.h>
0027 #include <services/ServicePluginManager.h>
0028 #include <statsyncing/Controller.h>
0029 #include <statsyncing/ProviderFactory.h>
0030 #include <storage/StorageFactory.h>
0031 
0032 #include <KLocalizedString>
0033 #include <KMessageBox>
0034 #include <KPluginLoader>
0035 
0036 #include <QGuiApplication>
0037 
0038 
0039 /** Defines the used plugin version number.
0040  *
0041  *  This must match the desktop files.
0042  */
0043 const int Plugins::PluginManager::s_pluginFrameworkVersion = 74;
0044 Plugins::PluginManager* Plugins::PluginManager::s_instance = nullptr;
0045 
0046 Plugins::PluginManager*
0047 Plugins::PluginManager::instance()
0048 {
0049     return s_instance ? s_instance : new PluginManager();
0050 }
0051 
0052 void
0053 Plugins::PluginManager::destroy()
0054 {
0055     if( s_instance )
0056     {
0057         delete s_instance;
0058         s_instance = nullptr;
0059     }
0060 }
0061 
0062 Plugins::PluginManager::PluginManager( QObject *parent )
0063     : QObject( parent )
0064 {
0065     DEBUG_BLOCK
0066     setObjectName( "PluginManager" );
0067     s_instance = this;
0068 
0069     PERF_LOG( "Initialising Plugin Manager" )
0070     init();
0071     PERF_LOG( "Initialised Plugin Manager" )
0072 }
0073 
0074 Plugins::PluginManager::~PluginManager()
0075 {
0076     // tell the managers to get rid of their current factories
0077     QList<QSharedPointer<Plugins::PluginFactory> > emptyFactories;
0078 
0079     StatSyncing::Controller *controller = Amarok::Components::statSyncingController();
0080     if( controller )
0081         controller->setFactories( emptyFactories );
0082     ServicePluginManager::instance()->setFactories( emptyFactories );
0083     CollectionManager::instance()->setFactories( emptyFactories );
0084     StorageManager::instance()->setFactories( emptyFactories );
0085 }
0086 
0087 void
0088 Plugins::PluginManager::init()
0089 {
0090     checkPluginEnabledStates();
0091 }
0092 
0093 KPluginInfo::List
0094 Plugins::PluginManager::plugins( Type type ) const
0095 {
0096     KPluginInfo::List infos;
0097 
0098     for( const auto &pluginInfo : m_pluginsByType.value( type ) )
0099     {
0100         auto info = KPluginInfo( pluginInfo );
0101         info.setConfig( Amarok::config( "Plugins" ) );
0102         infos << info;
0103     }
0104 
0105     return infos;
0106 }
0107 
0108 QVector<KPluginMetaData>
0109 Plugins::PluginManager::enabledPlugins(Plugins::PluginManager::Type type) const
0110 {
0111     QVector<KPluginMetaData> enabledList;
0112 
0113     for( const auto &plugin : m_pluginsByType.value( type ) )
0114     {
0115         if( isPluginEnabled( plugin ) )
0116             enabledList << plugin;
0117     }
0118 
0119     return enabledList;
0120 }
0121 
0122 QList<QSharedPointer<Plugins::PluginFactory> >
0123 Plugins::PluginManager::factories( Type type ) const
0124 {
0125     return m_factoriesByType.value( type );
0126 }
0127 
0128 void
0129 Plugins::PluginManager::checkPluginEnabledStates()
0130 {
0131     DEBUG_BLOCK
0132 
0133     // re-create all the member infos.
0134     m_plugins.clear();
0135     m_pluginsByType.clear();
0136     m_factoriesByType.clear();
0137 
0138     m_plugins = findPlugins(); // reload all the plugins plus their enabled state
0139 
0140     if( m_plugins.isEmpty() ) // exit if no plugins are found
0141     {
0142         if( qobject_cast<QGuiApplication*>( qApp ) )
0143         {
0144             KMessageBox::error( nullptr, i18n( "Amarok could not find any plugins. This indicates an installation problem." ) );
0145         }
0146         else
0147         {
0148             warning() << "Amarok could not find any plugins. Bailing out.";
0149         }
0150         // don't use QApplication::exit, as the eventloop may not have started yet
0151         std::exit( EXIT_SUCCESS );
0152     }
0153 
0154     // sort the plugin infos by type
0155     for( const auto &pluginInfo : m_plugins )
0156     {
0157         // create the factories and sort them by type
0158         auto factory = createFactory( pluginInfo );
0159 
0160         if( factory )
0161         {
0162             Type type;
0163 
0164             if( qobject_cast<StorageFactory*>( factory ) )
0165                 type = Storage;
0166             else if( qobject_cast<Collections::CollectionFactory*>( factory ) )
0167                 type = Collection;
0168             else if( qobject_cast<ServiceFactory*>( factory ) )
0169                 type = Service;
0170             else if( qobject_cast<StatSyncing::ProviderFactory*>( factory ) )
0171                 type = Importer;
0172             else
0173             {
0174                 warning() << pluginInfo.name() << "has unknown category";
0175                 warning() << pluginInfo.rawData().keys();
0176                 continue;
0177             }
0178 
0179             m_pluginsByType[ type ] << pluginInfo;
0180 
0181             if( isPluginEnabled( pluginInfo ) )
0182                 m_factoriesByType[ type ] << factory;
0183         }
0184         else
0185             warning() << pluginInfo.name() << "could not create factory";
0186     }
0187 
0188     // the setFactories functions should:
0189     // - filter out factories not useful (e.g. services when setting collections)
0190     // - handle the new list of factories, disabling old ones and enabling new ones.
0191 
0192 
0193     PERF_LOG( "Loading storage plugins" )
0194     StorageManager::instance()->setFactories( m_factoriesByType.value( Storage ) );
0195     PERF_LOG( "Loaded storage plugins" )
0196 
0197     PERF_LOG( "Loading collection plugins" )
0198     CollectionManager::instance()->setFactories( m_factoriesByType.value( Collection ) );
0199     PERF_LOG( "Loaded collection plugins" )
0200 
0201     PERF_LOG( "Loading service plugins" )
0202     ServicePluginManager::instance()->setFactories( m_factoriesByType.value( Service ) );
0203     PERF_LOG( "Loaded service plugins" )
0204 
0205     PERF_LOG( "Loading importer plugins" )
0206     StatSyncing::Controller *controller = Amarok::Components::statSyncingController();
0207     if( controller )
0208         controller->setFactories( m_factoriesByType.value( Importer ) );
0209     PERF_LOG( "Loaded importer plugins" )
0210 
0211     // init all new factories
0212     // do this after they were added to the sub-manager so that they
0213     // have a chance to connect to signals
0214     //
0215     // we need to init by type and the storages need to go first
0216     for( const auto &factory : m_factoriesByType[ Storage ] )
0217         factory->init();
0218     for( const auto &factory : m_factoriesByType[ Collection ] )
0219         factory->init();
0220     for( const auto &factory : m_factoriesByType[ Service ] )
0221         factory->init();
0222     for( const auto &factory : m_factoriesByType[ Importer ] )
0223         factory->init();
0224 }
0225 
0226 
0227 bool
0228 Plugins::PluginManager::isPluginEnabled( const KPluginMetaData &plugin ) const
0229 {
0230     // mysql storage and collection are vital. They need to be loaded always
0231 
0232     auto raw = plugin.rawData();
0233     int version = raw.value( "X-KDE-Amarok-framework-version" ).toInt();
0234     int rank = raw.value( "X-KDE-Amarok-rank" ).toInt();
0235 
0236     if( version != s_pluginFrameworkVersion )
0237     {
0238         warning() << "Plugin" << plugin.pluginId() << "has frameworks version" << version
0239                   << ". Version" << s_pluginFrameworkVersion << "is required";
0240         return false;
0241     }
0242 
0243     if( rank == 0 )
0244     {
0245         warning() << "Plugin" << plugin.pluginId() << "has rank 0";
0246         return false;
0247     }
0248 
0249     auto vital = raw.value( QStringLiteral( "X-KDE-Amarok-vital" ) );
0250 
0251     if( !vital.isUndefined())
0252     {
0253         if( vital.toBool() || vital.toString().toLower() == "true" )
0254         {
0255             debug() << "Plugin" << plugin.pluginId() << "is vital";
0256             return true;
0257         }
0258     }
0259 
0260     KPluginInfo info = KPluginInfo( plugin );
0261     info.setConfig( Amarok::config( "Plugins" ) );
0262     info.load();
0263 
0264     return info.isPluginEnabled();
0265 }
0266 
0267 
0268 QSharedPointer<Plugins::PluginFactory>
0269 Plugins::PluginManager::createFactory( const KPluginMetaData &pluginInfo )
0270 {
0271     debug() << "Creating factory for plugin:" << pluginInfo.pluginId();
0272 
0273     // check if we already created this factory
0274     // note: old factories are not deleted.
0275     //   We can't very well just destroy a factory being
0276     //   currently used.
0277     const QString name = pluginInfo.pluginId();
0278 
0279     if( m_factoryCreated.contains( name ) )
0280         return m_factoryCreated.value( name );
0281 
0282     QPluginLoader loader( pluginInfo.fileName() );
0283     auto pointer = qobject_cast<PluginFactory*>( loader.instance() );
0284     auto pluginFactory = QSharedPointer<Plugins::PluginFactory>( pointer );
0285 
0286     if( !pluginFactory )
0287     {
0288         warning() << QString( "Failed to get factory '%1' from QPluginLoader: %2" )
0289                      .arg( name, loader.errorString() );
0290         return QSharedPointer<Plugins::PluginFactory>();
0291     }
0292 
0293     m_factoryCreated[ name ] = pluginFactory;
0294     return pluginFactory;
0295 }
0296 
0297 
0298 QVector<KPluginMetaData>
0299 Plugins::PluginManager::findPlugins()
0300 {
0301     QVector<KPluginMetaData> plugins;
0302     for( const auto &location : QCoreApplication::libraryPaths() )
0303         plugins << KPluginLoader::findPlugins( location, [] ( const KPluginMetaData &metadata )
0304             { return metadata.serviceTypes().contains( QStringLiteral( "Amarok/Plugin" ) ); } );
0305 
0306     for( const auto &plugin : plugins )
0307     {
0308         bool enabled = isPluginEnabled( plugin );
0309         debug() << "found plugin:" << plugin.pluginId()
0310                 << "enabled:" << enabled;
0311     }
0312     debug() << plugins.count() << "plugins in total";
0313 
0314     return plugins;
0315 }
0316 
0317 int
0318 Plugins::PluginManager::pluginFrameworkVersion()
0319 {
0320     return s_pluginFrameworkVersion;
0321 }
0322