1
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
3
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
4
* Copyright 2012 Leo Franchi <lfranchi@kde.org>
5
* Copyright 2010-2011, Jeff Mitchell <jeff@tomahawk-player.org>
7
* Tomahawk is free software: you can redistribute it and/or modify
8
* it under the terms of the GNU General Public License as published by
9
* the Free Software Foundation, either version 3 of the License, or
10
* (at your option) any later version.
12
* Tomahawk is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
* GNU General Public License for more details.
17
* You should have received a copy of the GNU General Public License
18
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
21
#include "InfoSystemWorker.h"
24
#include "InfoSystemCache.h"
25
#include "GlobalActionManager.h"
26
#include "utils/TomahawkUtils.h"
27
#include "utils/Logger.h"
31
#include <QCoreApplication>
34
#include <QNetworkConfiguration>
35
#include <QNetworkProxy>
36
#include <QPluginLoader>
44
InfoSystemWorker::InfoSystemWorker()
47
tDebug() << Q_FUNC_INFO;
49
m_checkTimeoutsTimer.setInterval( 1000 );
50
m_checkTimeoutsTimer.setSingleShot( false );
51
connect( &m_checkTimeoutsTimer, SIGNAL( timeout() ), SLOT( checkTimeoutsTimerFired() ) );
52
m_checkTimeoutsTimer.start();
56
InfoSystemWorker::~InfoSystemWorker()
58
tDebug() << Q_FUNC_INFO << " beginning";
59
Q_FOREACH( InfoPluginPtr plugin, m_plugins )
64
tDebug() << Q_FUNC_INFO << " finished";
69
InfoSystemWorker::init( Tomahawk::InfoSystem::InfoSystemCache* cache )
71
tDebug() << Q_FUNC_INFO;
72
m_shortLinksWaiting = 0;
75
loadInfoPlugins( findInfoPlugins() );
80
InfoSystemWorker::addInfoPlugin( Tomahawk::InfoSystem::InfoPluginPtr plugin )
82
tDebug() << Q_FUNC_INFO << plugin;
83
foreach ( InfoPluginPtr ptr, m_plugins )
87
tDebug() << Q_FUNC_INFO << "This plugin is already added to the infosystem.";
92
if ( plugin.isNull() )
94
tDebug() << Q_FUNC_INFO << "passed-in plugin is null";
98
plugin.data()->moveToThread( this->thread() );
99
m_plugins.append( plugin );
100
registerInfoTypes( plugin, plugin.data()->supportedGetTypes(), plugin.data()->supportedPushTypes() );
104
SIGNAL( info( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ),
106
SLOT( infoSlot( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ),
112
SIGNAL( getCachedInfo( Tomahawk::InfoSystem::InfoStringHash, qint64, Tomahawk::InfoSystem::InfoRequestData ) ),
114
SLOT( getCachedInfoSlot( Tomahawk::InfoSystem::InfoStringHash, qint64, Tomahawk::InfoSystem::InfoRequestData ) ),
119
SIGNAL( updateCache( Tomahawk::InfoSystem::InfoStringHash, qint64, Tomahawk::InfoSystem::InfoType, QVariant ) ),
121
SLOT( updateCacheSlot( Tomahawk::InfoSystem::InfoStringHash, qint64, Tomahawk::InfoSystem::InfoType, QVariant ) ),
125
QMetaObject::invokeMethod( plugin.data(), "init", Qt::QueuedConnection );
127
emit updatedSupportedGetTypes( QSet< InfoType >::fromList( m_infoGetMap.keys() ) );
128
emit updatedSupportedPushTypes( QSet< InfoType >::fromList( m_infoPushMap.keys() ) );
133
InfoSystemWorker::removeInfoPlugin( Tomahawk::InfoSystem::InfoPluginPtr plugin )
135
tDebug() << Q_FUNC_INFO << plugin;
137
if ( plugin.isNull() )
139
tDebug() << Q_FUNC_INFO << "passed-in plugin is null";
143
foreach ( InfoPluginPtr ptr, m_plugins )
148
tDebug() << Q_FUNC_INFO << "This plugin does not exist in the infosystem.";
152
m_plugins.removeOne( plugin );
153
deregisterInfoTypes( plugin, plugin.data()->supportedGetTypes(), plugin.data()->supportedPushTypes() );
154
delete plugin.data();
159
InfoSystemWorker::findInfoPlugins()
162
QList< QDir > pluginDirs;
164
QDir appDir( qApp->applicationDirPath() );
166
if ( appDir.dirName() == "MacOS" )
168
// Development convenience-hack
175
QDir libDir( CMAKE_INSTALL_PREFIX "/lib" );
177
QDir lib64Dir( appDir );
179
lib64Dir.cd( "lib64" );
181
pluginDirs << appDir << libDir << lib64Dir << QDir( qApp->applicationDirPath() );
182
foreach ( const QDir& pluginDir, pluginDirs )
184
tDebug() << Q_FUNC_INFO << "Checking directory for plugins:" << pluginDir;
185
foreach ( QString fileName, pluginDir.entryList( QStringList() << "*tomahawk_infoplugin_*.so" << "*tomahawk_infoplugin_*.dylib" << "*tomahawk_infoplugin_*.dll", QDir::Files ) )
187
if ( fileName.startsWith( "libtomahawk_infoplugin" ) )
189
const QString path = pluginDir.absoluteFilePath( fileName );
190
if ( !paths.contains( path ) )
201
InfoSystemWorker::loadInfoPlugins( const QStringList& pluginPaths )
203
tDebug() << Q_FUNC_INFO << "Attempting to load the following plugin paths:" << pluginPaths;
205
if ( pluginPaths.isEmpty() )
208
foreach ( const QString fileName, pluginPaths )
210
if ( !QLibrary::isLibrary( fileName ) )
213
tDebug() << Q_FUNC_INFO << "Trying to load plugin:" << fileName;
215
QPluginLoader loader( fileName );
216
QObject* plugin = loader.instance();
219
tDebug() << Q_FUNC_INFO << "Error loading plugin:" << loader.errorString();
223
InfoPlugin* infoPlugin = qobject_cast< InfoPlugin* >( plugin );
226
tDebug() << Q_FUNC_INFO << "Loaded info plugin:" << loader.fileName();
227
addInfoPlugin( InfoPluginPtr( infoPlugin ) );
230
tDebug() << Q_FUNC_INFO << "Loaded invalid plugin:" << loader.fileName();
236
InfoSystemWorker::registerInfoTypes( const InfoPluginPtr &plugin, const QSet< InfoType >& getTypes, const QSet< InfoType >& pushTypes )
238
Q_FOREACH( InfoType type, getTypes )
239
m_infoGetMap[type].append( plugin );
240
Q_FOREACH( InfoType type, pushTypes )
241
m_infoPushMap[type].append( plugin );
246
InfoSystemWorker::deregisterInfoTypes( const InfoPluginPtr &plugin, const QSet< InfoType >& getTypes, const QSet< InfoType >& pushTypes )
248
Q_FOREACH( InfoType type, getTypes )
249
m_infoGetMap[type].removeOne( plugin );
250
Q_FOREACH( InfoType type, pushTypes )
251
m_infoPushMap[type].removeOne( plugin );
255
QList< InfoPluginPtr >
256
InfoSystemWorker::determineOrderedMatches( const InfoType type ) const
258
//Dummy function for now that returns the various items in the QSet; at some point this will
259
//probably need to support ordering based on the data source
260
QList< InfoPluginPtr > providers;
261
Q_FOREACH( InfoPluginPtr ptr, m_infoGetMap[type] )
268
InfoSystemWorker::getInfo( Tomahawk::InfoSystem::InfoRequestData requestData )
270
//qDebug() << Q_FUNC_INFO << "type is " << requestData.type << " and allSources = " << (allSources ? "true" : "false" );
272
QList< InfoPluginPtr > providers = determineOrderedMatches( requestData.type );
273
if ( providers.isEmpty() )
275
emit info( requestData, QVariant() );
276
checkFinished( requestData );
280
if ( !requestData.allSources )
281
providers = QList< InfoPluginPtr >( providers.mid( 0, 1 ) );
283
bool foundOne = false;
284
foreach ( InfoPluginPtr ptr, providers )
291
if ( requestData.allSources || m_savedRequestMap.contains( requestData.requestId ) )
293
if ( m_savedRequestMap.contains( requestData.requestId ) )
294
tDebug() << Q_FUNC_INFO << "Warning: reassigning requestId because it already exists";
295
requestData.internalId = TomahawkUtils::infosystemRequestId();
298
requestData.internalId = requestData.requestId;
300
quint64 requestId = requestData.internalId;
301
m_requestSatisfiedMap[ requestId ] = false;
302
if ( requestData.timeoutMillis != 0 )
304
qint64 currMs = QDateTime::currentMSecsSinceEpoch();
305
m_timeRequestMapper.insert( currMs + requestData.timeoutMillis, requestId );
307
// qDebug() << "Assigning request with requestId" << requestId << "and type" << requestData.type;
308
m_dataTracker[ requestData.caller ][ requestData.type ] = m_dataTracker[ requestData.caller ][ requestData.type ] + 1;
309
// qDebug() << "Current count in dataTracker for target" << requestData.caller << "and type" << requestData.type << "is" << m_dataTracker[ requestData.caller ][ requestData.type ];
311
InfoRequestData* data = new InfoRequestData;
312
data->caller = requestData.caller;
313
data->type = requestData.type;
314
data->input = requestData.input;
315
data->customData = requestData.customData;
316
m_savedRequestMap[ requestId ] = data;
318
QMetaObject::invokeMethod( ptr.data(), "getInfo", Qt::QueuedConnection, Q_ARG( Tomahawk::InfoSystem::InfoRequestData, requestData ) );
323
emit info( requestData, QVariant() );
324
checkFinished( requestData );
330
InfoSystemWorker::pushInfo( Tomahawk::InfoSystem::InfoPushData pushData )
332
tDebug() << Q_FUNC_INFO << "type is " << pushData.type;
334
if ( pushData.pushFlags != PushNoFlag )
336
if ( pushData.pushFlags & PushShortUrlFlag )
338
pushData.pushFlags = Tomahawk::InfoSystem::PushInfoFlags( pushData.pushFlags & ~PushShortUrlFlag );
339
QMetaObject::invokeMethod( this, "getShortUrl", Qt::QueuedConnection, Q_ARG( Tomahawk::InfoSystem::InfoPushData, pushData ) );
344
tDebug() << Q_FUNC_INFO << "number of matching plugins: " << m_infoPushMap[ pushData.type ].size();
346
Q_FOREACH( InfoPluginPtr ptr, m_infoPushMap[ pushData.type ] )
349
QMetaObject::invokeMethod( ptr.data(), "pushInfo", Qt::QueuedConnection, Q_ARG( Tomahawk::InfoSystem::InfoPushData, pushData ) );
355
InfoSystemWorker::getShortUrl( Tomahawk::InfoSystem::InfoPushData pushData )
357
tDebug() << Q_FUNC_INFO << "type is " << pushData.type;
358
if ( !pushData.infoPair.second.canConvert< Tomahawk::InfoSystem::InfoStringHash >() )
360
QMetaObject::invokeMethod( this, "pushInfo", Qt::QueuedConnection, Q_ARG( Tomahawk::InfoSystem::InfoPushData, pushData ) );
364
Tomahawk::InfoSystem::InfoStringHash hash = pushData.infoPair.second.value< Tomahawk::InfoSystem::InfoStringHash >();
366
if ( hash.isEmpty() || !hash.contains( "title" ) || !hash.contains( "artist" ) )
368
QMetaObject::invokeMethod( this, "pushInfo", Qt::QueuedConnection, Q_ARG( Tomahawk::InfoSystem::InfoPushData, pushData ) );
372
QString title, artist, album;
373
title = hash[ "title" ];
374
artist = hash[ "artist" ];
375
if( hash.contains( "album" ) )
376
album = hash[ "album" ];
378
QUrl longUrl = GlobalActionManager::instance()->openLink( title, artist, album );
380
GlobalActionManager::instance()->shortenLink( longUrl, QVariant::fromValue< Tomahawk::InfoSystem::InfoPushData >( pushData ) );
381
connect( GlobalActionManager::instance(), SIGNAL( shortLinkReady( QUrl, QUrl, QVariant ) ), this, SLOT( shortLinkReady( QUrl, QUrl, QVariant ) ), Qt::UniqueConnection );
382
m_shortLinksWaiting++;
387
InfoSystemWorker::shortLinkReady( QUrl longUrl, QUrl shortUrl, QVariant callbackObj )
389
tDebug() << Q_FUNC_INFO << "long url = " << longUrl << ", shortUrl = " << shortUrl;
390
m_shortLinksWaiting--;
391
if ( !m_shortLinksWaiting )
392
disconnect( GlobalActionManager::instance(), SIGNAL( shortLinkReady( QUrl, QUrl, QVariant ) ) );
394
if ( !callbackObj.isValid() )
396
tDebug() << Q_FUNC_INFO << "callback object was not valid, cannot continue";
400
Tomahawk::InfoSystem::InfoPushData pushData = callbackObj.value< Tomahawk::InfoSystem::InfoPushData >();
402
if ( !shortUrl.isEmpty() && longUrl != shortUrl )
404
QVariantMap flagProps = pushData.infoPair.first;
405
flagProps[ "shorturl" ] = shortUrl;
406
pushData.infoPair.first = flagProps;
409
tDebug() << Q_FUNC_INFO << "pushInfoPair first is: " << pushData.infoPair.first.keys();
411
QMetaObject::invokeMethod( this, "pushInfo", Qt::QueuedConnection, Q_ARG( Tomahawk::InfoSystem::InfoPushData, pushData ) );
416
InfoSystemWorker::infoSlot( Tomahawk::InfoSystem::InfoRequestData requestData, QVariant output )
418
// qDebug() << Q_FUNC_INFO << "with requestId" << requestId;
420
quint64 requestId = requestData.internalId;
422
if ( m_dataTracker[ requestData.caller ][ requestData.type ] == 0 )
424
// qDebug() << Q_FUNC_INFO << "Caller was not waiting for that type of data!";
427
if ( !m_requestSatisfiedMap.contains( requestId ) || m_requestSatisfiedMap[ requestId ] )
429
// qDebug() << Q_FUNC_INFO << "Request was already taken care of!";
433
m_requestSatisfiedMap[ requestId ] = true;
434
emit info( requestData, output );
436
m_dataTracker[ requestData.caller ][ requestData.type ] = m_dataTracker[ requestData.caller ][ requestData.type ] - 1;
437
// qDebug() << "Current count in dataTracker for target" << requestData.caller << "and type" << requestData.type << "is" << m_dataTracker[ requestData.caller ][ requestData.type ];
438
delete m_savedRequestMap[ requestId ];
439
m_savedRequestMap.remove( requestId );
440
checkFinished( requestData );
445
InfoSystemWorker::checkFinished( const Tomahawk::InfoSystem::InfoRequestData &requestData )
447
if ( m_dataTracker[ requestData.caller ][ requestData.type ] == 0 )
448
emit finished( requestData.caller, requestData.type );
450
Q_FOREACH( InfoType testtype, m_dataTracker[ requestData.caller ].keys() )
452
if ( m_dataTracker[ requestData.caller ][ testtype ] != 0 )
455
// qDebug() << "Emitting finished with target" << target;
456
emit finished( requestData.caller );
461
InfoSystemWorker::checkTimeoutsTimerFired()
463
qint64 currTime = QDateTime::currentMSecsSinceEpoch();
464
Q_FOREACH( qint64 time, m_timeRequestMapper.keys() )
466
Q_FOREACH( quint64 requestId, m_timeRequestMapper.values( time ) )
468
if ( time < currTime )
470
if ( m_requestSatisfiedMap[ requestId ] )
472
// qDebug() << Q_FUNC_INFO << "Removing mapping of" << requestId << "which expired at time" << time << "and was already satisfied";
473
m_timeRequestMapper.remove( time, requestId );
474
if ( !m_timeRequestMapper.count( time ) )
475
m_timeRequestMapper.remove( time );
480
// qDebug() << Q_FUNC_INFO << "Doh, timed out for requestId" << requestId;
481
InfoRequestData *savedData = m_savedRequestMap[ requestId ];
483
InfoRequestData returnData;
484
returnData.caller = savedData->caller;
485
returnData.type = savedData->type;
486
returnData.input = savedData->input;
487
returnData.customData = savedData->customData;
488
emit info( returnData, QVariant() );
491
m_savedRequestMap.remove( requestId );
493
m_dataTracker[ returnData.caller ][ returnData.type ] = m_dataTracker[ returnData.caller ][ returnData.type ] - 1;
494
// qDebug() << "Current count in dataTracker for target" << returnData.caller << "is" << m_dataTracker[ returnData.caller ][ returnData.type ];
496
m_requestSatisfiedMap[ requestId ] = true;
497
m_timeRequestMapper.remove( time, requestId );
498
if ( !m_timeRequestMapper.count( time ) )
499
m_timeRequestMapper.remove( time );
501
checkFinished( returnData );
505
//we've caught up, the remaining requets still have time to work
513
} //namespace InfoSystem
515
} //namespace Tomahawk