/src/libtomahawk/Pipeline.cpp

http://github.com/tomahawk-player/tomahawk · C++ · 717 lines · 513 code · 157 blank · 47 comment · 59 complexity · 7e0ea52bab968b8952fc4713a839b0e0 MD5 · raw file

  1. /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
  2. *
  3. * Copyright 2010-2015, Christian Muehlhaeuser <muesli@tomahawk-player.org>
  4. *
  5. * Tomahawk is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * Tomahawk is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. #include "Pipeline_p.h"
  19. #include <QMutexLocker>
  20. #include "database/Database.h"
  21. #include "resolvers/ExternalResolver.h"
  22. #include "resolvers/ScriptResolver.h"
  23. #include "resolvers/JSResolver.h"
  24. #include "utils/ResultUrlChecker.h"
  25. #include "utils/Logger.h"
  26. #include "FuncTimeout.h"
  27. #include "Result.h"
  28. #include "Source.h"
  29. #include "SourceList.h"
  30. #define DEFAULT_CONCURRENT_QUERIES 4
  31. #define MAX_CONCURRENT_QUERIES 16
  32. #define CLEANUP_TIMEOUT 5 * 60 * 1000
  33. #define MINSCORE 0.5
  34. #define DEFAULT_RESOLVER_TIMEOUT 5000 // 5 seconds
  35. using namespace Tomahawk;
  36. Pipeline* PipelinePrivate::s_instance = 0;
  37. Pipeline*
  38. Pipeline::instance()
  39. {
  40. return PipelinePrivate::s_instance;
  41. }
  42. Pipeline::Pipeline( QObject* parent )
  43. : QObject( parent )
  44. , d_ptr( new PipelinePrivate( this ) )
  45. {
  46. Q_D( Pipeline );
  47. PipelinePrivate::s_instance = this;
  48. d->maxConcurrentQueries = 24;
  49. tDebug() << Q_FUNC_INFO << "Using" << d->maxConcurrentQueries << "threads";
  50. d->temporaryQueryTimer.setInterval( CLEANUP_TIMEOUT );
  51. connect( &d->temporaryQueryTimer, SIGNAL( timeout() ), SLOT( onTemporaryQueryTimer() ) );
  52. }
  53. Pipeline::~Pipeline()
  54. {
  55. Q_D( Pipeline );
  56. tDebug() << Q_FUNC_INFO;
  57. d->running = false;
  58. // stop script resolvers
  59. foreach ( QPointer< ExternalResolver > r, d->scriptResolvers )
  60. if ( !r.isNull() )
  61. r.data()->deleteLater();
  62. d->scriptResolvers.clear();
  63. }
  64. bool
  65. Pipeline::isRunning() const
  66. {
  67. Q_D( const Pipeline );
  68. return d->running;
  69. }
  70. unsigned int
  71. Pipeline::pendingQueryCount() const
  72. {
  73. Q_D( const Pipeline );
  74. return d->queries_pending.count();
  75. }
  76. unsigned int
  77. Pipeline::activeQueryCount() const
  78. {
  79. Q_D( const Pipeline );
  80. return d->qidsState.uniqueKeys().count();
  81. }
  82. void
  83. Pipeline::databaseReady()
  84. {
  85. connect( Database::instance(), SIGNAL( ready() ), this, SLOT( start() ), Qt::QueuedConnection );
  86. Database::instance()->loadIndex();
  87. }
  88. void
  89. Pipeline::start()
  90. {
  91. Q_D( Pipeline );
  92. tDebug() << Q_FUNC_INFO << "Shunting" << d->queries_pending.size() << "queries!";
  93. d->running = true;
  94. emit running();
  95. shuntNext();
  96. }
  97. void
  98. Pipeline::stop()
  99. {
  100. Q_D( Pipeline );
  101. d->running = false;
  102. }
  103. void
  104. Pipeline::removeResolver( Resolver* r )
  105. {
  106. Q_D( Pipeline );
  107. QMutexLocker lock( &d->mut );
  108. tDebug() << "Removed resolver:" << r->name();
  109. d->resolvers.removeAll( r );
  110. if ( d->running ) {
  111. // Only notify if Pipeline is still active.
  112. emit resolverRemoved( r );
  113. }
  114. }
  115. QList< Tomahawk::Resolver* >
  116. Pipeline::resolvers() const
  117. {
  118. Q_D( const Pipeline );
  119. return d->resolvers;
  120. }
  121. void
  122. Pipeline::addResolver( Resolver* r )
  123. {
  124. Q_D( Pipeline );
  125. QMutexLocker lock( &d->mut );
  126. tDebug() << "Adding resolver" << r->name();
  127. d->resolvers.append( r );
  128. emit resolverAdded( r );
  129. }
  130. void
  131. Pipeline::addExternalResolverFactory( ResolverFactoryFunc resolverFactory )
  132. {
  133. Q_D( Pipeline );
  134. d->resolverFactories << resolverFactory;
  135. }
  136. Tomahawk::ExternalResolver*
  137. Pipeline::addScriptResolver( const QString& accountId, const QString& path, const QStringList& additionalScriptPaths )
  138. {
  139. Q_D( Pipeline );
  140. ExternalResolver* res = 0;
  141. foreach ( ResolverFactoryFunc factory, d->resolverFactories )
  142. {
  143. res = factory( accountId, path, additionalScriptPaths );
  144. if ( !res )
  145. continue;
  146. d->scriptResolvers << QPointer< ExternalResolver > ( res );
  147. break;
  148. }
  149. return res;
  150. }
  151. void
  152. Pipeline::stopScriptResolver( const QString& path )
  153. {
  154. Q_D( Pipeline );
  155. foreach ( QPointer< ExternalResolver > res, d->scriptResolvers )
  156. {
  157. if ( res.data()->filePath() == path )
  158. res.data()->stop();
  159. }
  160. }
  161. void
  162. Pipeline::removeScriptResolver( const QString& scriptPath )
  163. {
  164. Q_D( Pipeline );
  165. QPointer< ExternalResolver > r;
  166. foreach ( QPointer< ExternalResolver > res, d->scriptResolvers )
  167. {
  168. if ( res.isNull() )
  169. continue;
  170. if ( res.data()->filePath() == scriptPath )
  171. r = res;
  172. }
  173. d->scriptResolvers.removeAll( r );
  174. if ( !r.isNull() )
  175. {
  176. r.data()->stop();
  177. r.data()->deleteLater();
  178. }
  179. }
  180. QList<QPointer<ExternalResolver> >
  181. Pipeline::scriptResolvers() const
  182. {
  183. Q_D( const Pipeline );
  184. return d->scriptResolvers;
  185. }
  186. ExternalResolver*
  187. Pipeline::resolverForPath( const QString& scriptPath )
  188. {
  189. Q_D( Pipeline );
  190. foreach ( QPointer< ExternalResolver > res, d->scriptResolvers )
  191. {
  192. if ( res.data()->filePath() == scriptPath )
  193. return res.data();
  194. }
  195. return 0;
  196. }
  197. void
  198. Pipeline::resolve( const QList<query_ptr>& qlist, bool prioritized, bool temporaryQuery )
  199. {
  200. Q_D( Pipeline );
  201. {
  202. QMutexLocker lock( &d->mut );
  203. int i = 0;
  204. foreach ( const query_ptr& q, qlist )
  205. {
  206. if ( q->resolvingFinished() )
  207. continue;
  208. if ( d->qidsState.contains( q->id() ) )
  209. continue;
  210. if ( d->queries_pending.contains( q ) )
  211. {
  212. if ( prioritized )
  213. {
  214. d->queries_pending.insert( i++, d->queries_pending.takeAt( d->queries_pending.indexOf( q ) ) );
  215. }
  216. continue;
  217. }
  218. if ( !d->qids.contains( q->id() ) )
  219. d->qids.insert( q->id(), q );
  220. if ( prioritized )
  221. d->queries_pending.insert( i++, q );
  222. else
  223. d->queries_pending << q;
  224. if ( temporaryQuery )
  225. {
  226. d->queries_temporary << q;
  227. if ( d->temporaryQueryTimer.isActive() )
  228. d->temporaryQueryTimer.stop();
  229. d->temporaryQueryTimer.start();
  230. }
  231. }
  232. }
  233. shuntNext();
  234. }
  235. bool
  236. Pipeline::isResolving( const query_ptr& q ) const
  237. {
  238. Q_D( const Pipeline );
  239. return d->qids.contains( q->id() ) && d->qidsState.contains( q->id() );
  240. }
  241. void
  242. Pipeline::resolve( const query_ptr& q, bool prioritized, bool temporaryQuery )
  243. {
  244. if ( q.isNull() )
  245. return;
  246. QList< query_ptr > qlist;
  247. qlist << q;
  248. resolve( qlist, prioritized, temporaryQuery );
  249. }
  250. void
  251. Pipeline::resolve( QID qid, bool prioritized, bool temporaryQuery )
  252. {
  253. resolve( query( qid ), prioritized, temporaryQuery );
  254. }
  255. void
  256. Pipeline::reportError( QID qid, Tomahawk::Resolver* r )
  257. {
  258. reportResults( qid, r, QList< result_ptr>() );
  259. }
  260. void
  261. Pipeline::reportResults( QID qid, Tomahawk::Resolver* r, const QList< result_ptr >& results )
  262. {
  263. Q_D( Pipeline );
  264. if ( !d->running )
  265. return;
  266. if ( !d->qids.contains( qid ) )
  267. {
  268. if ( !results.isEmpty() )
  269. {
  270. Resolver* resolvedBy = results[0]->resolvedBy();
  271. if ( resolvedBy )
  272. {
  273. tDebug() << "Result arrived too late for:" << qid << "by" << resolvedBy->name();
  274. }
  275. else
  276. {
  277. tDebug() << "Result arrived too late for:" << qid;
  278. }
  279. }
  280. return;
  281. }
  282. const query_ptr& q = d->qids.value( qid );
  283. Q_ASSERT( !q.isNull() );
  284. if ( q.isNull() )
  285. return;
  286. QList< result_ptr > cleanResults;
  287. QList< result_ptr > httpResults;
  288. foreach ( const result_ptr& r, results )
  289. {
  290. if ( !r )
  291. continue;
  292. if ( !r->checked() && ( r->url().startsWith( "http" ) && !r->url().startsWith( "http://localhost" ) ) )
  293. httpResults << r;
  294. else
  295. cleanResults << r;
  296. }
  297. addResultsToQuery( q, cleanResults );
  298. if ( !httpResults.isEmpty() )
  299. {
  300. const ResultUrlChecker* checker = new ResultUrlChecker( q, r, httpResults );
  301. connect( checker, SIGNAL( done() ), SLOT( onResultUrlCheckerDone() ) );
  302. }
  303. else
  304. {
  305. decQIDState( q, r );
  306. }
  307. /* if ( q->solved() && !q->isFullTextQuery() )
  308. {
  309. checkQIDState( q, 0 );
  310. return;
  311. }*/
  312. }
  313. void
  314. Pipeline::addResultsToQuery( const query_ptr& query, const QList< result_ptr >& results )
  315. {
  316. Q_D( Pipeline );
  317. // tDebug( LOGVERBOSE ) << Q_FUNC_INFO << query->toString() << results.count();
  318. QList< result_ptr > cleanResults;
  319. foreach ( const result_ptr& r, results )
  320. {
  321. if ( !query->isFullTextQuery() && query->howSimilar( r ) < MINSCORE )
  322. continue;
  323. cleanResults << r;
  324. }
  325. if ( !cleanResults.isEmpty() )
  326. {
  327. query->addResults( cleanResults );
  328. if ( d->queries_temporary.contains( query ) )
  329. {
  330. foreach ( const result_ptr& r, cleanResults )
  331. {
  332. d->rids.insert( r->id(), r );
  333. }
  334. }
  335. }
  336. }
  337. void
  338. Pipeline::onResultUrlCheckerDone()
  339. {
  340. ResultUrlChecker* checker = qobject_cast< ResultUrlChecker* >( sender() );
  341. if ( !checker )
  342. return;
  343. checker->deleteLater();
  344. const query_ptr q = checker->query();
  345. addResultsToQuery( q, checker->validResults() );
  346. /* if ( q && !q->isFullTextQuery() )
  347. {
  348. checkQIDState( q, 0 );
  349. return;
  350. }*/
  351. decQIDState( q, reinterpret_cast<Tomahawk::Resolver*>( checker->userData() ) );
  352. }
  353. void
  354. Pipeline::reportAlbums( QID qid, const QList< album_ptr >& albums )
  355. {
  356. Q_D( Pipeline );
  357. if ( !d->running )
  358. return;
  359. if ( !d->qids.contains( qid ) )
  360. {
  361. tDebug() << "Albums arrived too late for:" << qid;
  362. return;
  363. }
  364. const query_ptr& q = d->qids.value( qid );
  365. Q_ASSERT( q->isFullTextQuery() );
  366. QList< album_ptr > cleanAlbums;
  367. foreach ( const album_ptr& r, albums )
  368. {
  369. // float score = q->howSimilar( r );
  370. cleanAlbums << r;
  371. }
  372. if ( !cleanAlbums.isEmpty() )
  373. {
  374. q->addAlbums( cleanAlbums );
  375. }
  376. }
  377. void
  378. Pipeline::reportArtists( QID qid, const QList< artist_ptr >& artists )
  379. {
  380. Q_D( Pipeline );
  381. if ( !d->running )
  382. return;
  383. if ( !d->qids.contains( qid ) )
  384. {
  385. tDebug() << "Artists arrived too late for:" << qid;
  386. return;
  387. }
  388. const query_ptr& q = d->qids.value( qid );
  389. Q_ASSERT( q->isFullTextQuery() );
  390. QList< artist_ptr > cleanArtists;
  391. foreach ( const artist_ptr& r, artists )
  392. {
  393. // float score = q->howSimilar( r );
  394. cleanArtists << r;
  395. }
  396. if ( !cleanArtists.isEmpty() )
  397. {
  398. q->addArtists( cleanArtists );
  399. }
  400. }
  401. void
  402. Pipeline::shuntNext()
  403. {
  404. Q_D( Pipeline );
  405. if ( !d->running )
  406. return;
  407. unsigned int rc;
  408. query_ptr q;
  409. {
  410. QMutexLocker lock( &d->mut );
  411. rc = d->resolvers.count();
  412. if ( d->queries_pending.isEmpty() )
  413. {
  414. if ( d->qidsState.isEmpty() )
  415. emit idle();
  416. return;
  417. }
  418. // Check if we are ready to dispatch more queries
  419. if ( activeQueryCount() >= d->maxConcurrentQueries )
  420. return;
  421. /*
  422. Since resolvers are async, we now dispatch to the highest weighted ones
  423. and after timeout, dispatch to next highest etc, aborting when solved
  424. */
  425. q = d->queries_pending.takeFirst();
  426. q->setCurrentResolver( 0 );
  427. }
  428. // Zero-patient, a stub so that query is not resolved until we go through
  429. // all resolvers
  430. // As query considered as 'finished trying to resolve' when there are no
  431. // more qid entries in qidsState we'll put one as sort of 'keep this until
  432. // we kick off all our resolvers' entry
  433. // once we kick off all resolvers we'll remove this entry
  434. incQIDState( q, nullptr );
  435. checkQIDState( q );
  436. }
  437. void
  438. Pipeline::timeoutShunt( const query_ptr& q, Tomahawk::Resolver* r )
  439. {
  440. Q_D( Pipeline );
  441. if ( !d->running )
  442. return;
  443. decQIDState( q, r );
  444. }
  445. void
  446. Pipeline::shunt( const query_ptr& q )
  447. {
  448. Q_D( Pipeline );
  449. if ( !d->running )
  450. return;
  451. Resolver* r = 0;
  452. if ( !q->resolvingFinished() )
  453. r = nextResolver( q );
  454. if ( r )
  455. {
  456. tLog( LOGVERBOSE ) << "Dispatching to resolver" << r->name() << r->timeout() << q->toString() << q->solved() << q->id();
  457. incQIDState( q, r );
  458. q->setCurrentResolver( r );
  459. r->resolve( q );
  460. emit resolving( q );
  461. auto timeout = r->timeout();
  462. if ( timeout == 0 )
  463. timeout = DEFAULT_RESOLVER_TIMEOUT;
  464. new FuncTimeout( timeout, std::bind( &Pipeline::timeoutShunt, this, q, r ), this );
  465. }
  466. else
  467. {
  468. // we get here if we disable a resolver while a query is resolving
  469. // OR we are just out of resolvers while query is still resolving
  470. // since we seem to at least tried to kick off all of the resolvers,
  471. // remove the '.keep' entry
  472. decQIDState( q, nullptr );
  473. return;
  474. }
  475. shuntNext();
  476. }
  477. Tomahawk::Resolver*
  478. Pipeline::nextResolver( const Tomahawk::query_ptr& query ) const
  479. {
  480. Q_D( const Pipeline );
  481. Resolver* newResolver = 0;
  482. foreach ( Resolver* r, d->resolvers )
  483. {
  484. if ( query->resolvedBy().contains( r ) )
  485. continue;
  486. if ( !newResolver )
  487. {
  488. newResolver = r;
  489. continue;
  490. }
  491. if ( r->weight() > newResolver->weight() )
  492. newResolver = r;
  493. }
  494. return newResolver;
  495. }
  496. void
  497. Pipeline::checkQIDState( const Tomahawk::query_ptr& query )
  498. {
  499. Q_D( Pipeline );
  500. QMutexLocker lock( &d->mut );
  501. tDebug() << Q_FUNC_INFO << query->id() << d->qidsState.count( query->id() );
  502. if ( d->qidsState.contains( query->id() ) )
  503. {
  504. new FuncTimeout( 0, std::bind( &Pipeline::shunt, this, query ), this );
  505. }
  506. else
  507. {
  508. query->onResolvingFinished();
  509. if ( !d->queries_temporary.contains( query ) )
  510. d->qids.remove( query->id() );
  511. new FuncTimeout( 0, std::bind( &Pipeline::shuntNext, this ), this );
  512. }
  513. }
  514. void
  515. Pipeline::incQIDState( const Tomahawk::query_ptr& query, Tomahawk::Resolver* r )
  516. {
  517. Q_D( Pipeline );
  518. QMutexLocker lock( &d->mut );
  519. d->qidsState.insert( query->id(), r );
  520. }
  521. void
  522. Pipeline::decQIDState( const Tomahawk::query_ptr& query, Tomahawk::Resolver* r )
  523. {
  524. Q_D( Pipeline );
  525. if ( d->qidsState.contains( query->id(), r ) )
  526. {
  527. {
  528. QMutexLocker lock( &d->mut );
  529. d->qidsState.remove( query->id(), r ); // Removes all matching pairs
  530. }
  531. checkQIDState( query );
  532. }
  533. }
  534. void
  535. Pipeline::onTemporaryQueryTimer()
  536. {
  537. Q_D( Pipeline );
  538. tDebug() << Q_FUNC_INFO;
  539. QMutexLocker lock( &d->mut );
  540. d->temporaryQueryTimer.stop();
  541. for ( int i = d->queries_temporary.count() - 1; i >= 0; i-- )
  542. {
  543. query_ptr q = d->queries_temporary.takeAt( i );
  544. d->qids.remove( q->id() );
  545. foreach ( const Tomahawk::result_ptr& r, q->results() )
  546. d->rids.remove( r->id() );
  547. }
  548. }
  549. query_ptr
  550. Pipeline::query( const QID& qid ) const
  551. {
  552. Q_D( const Pipeline );
  553. return d->qids.value( qid );
  554. }
  555. result_ptr
  556. Pipeline::result( const RID& rid ) const
  557. {
  558. Q_D( const Pipeline );
  559. return d->rids.value( rid );
  560. }