/src/libtomahawk/Query.cpp

http://github.com/tomahawk-player/tomahawk · C++ · 792 lines · 587 code · 166 blank · 39 comment · 64 complexity · 64a2e7f87f55d9d8b0f69e72a3302fb6 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. * Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
  5. * Copyright 2013, Uwe L. Korn <uwelk@xhochy.com>
  6. *
  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.
  11. *
  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.
  16. *
  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/>.
  19. */
  20. #include "Query_p.h"
  21. #include "audio/AudioEngine.h"
  22. #include "collection/Collection.h"
  23. #include "database/Database.h"
  24. #include "database/DatabaseImpl.h"
  25. #include "resolvers/Resolver.h"
  26. #include "utils/Logger.h"
  27. #include "Album.h"
  28. #include "Pipeline.h"
  29. #include "Result.h"
  30. #include <QtAlgorithms>
  31. #include <QDebug>
  32. #include <QCoreApplication>
  33. using namespace Tomahawk;
  34. query_ptr
  35. Query::get( const QString& artist, const QString& track, const QString& album, const QID& qid, bool autoResolve )
  36. {
  37. if ( artist.trimmed().isEmpty() || track.trimmed().isEmpty() )
  38. return query_ptr();
  39. if ( qid.isEmpty() )
  40. autoResolve = false;
  41. query_ptr q = query_ptr( new Query( Track::get( artist, track, album ), qid, autoResolve ), &QObject::deleteLater );
  42. q->moveToThread( QCoreApplication::instance()->thread() );
  43. q->setWeakRef( q.toWeakRef() );
  44. if ( autoResolve )
  45. Pipeline::instance()->resolve( q );
  46. return q;
  47. }
  48. query_ptr
  49. Query::get( const Tomahawk::track_ptr& track, const QID& qid )
  50. {
  51. query_ptr q = query_ptr( new Query( track, qid, false ), &QObject::deleteLater );
  52. q->setWeakRef( q.toWeakRef() );
  53. return q;
  54. }
  55. query_ptr
  56. Query::get( const QString& query, const QID& qid )
  57. {
  58. Q_ASSERT( !query.trimmed().isEmpty() );
  59. query_ptr q = query_ptr( new Query( query, qid ), &QObject::deleteLater );
  60. q->setWeakRef( q.toWeakRef() );
  61. if ( !qid.isEmpty() )
  62. Pipeline::instance()->resolve( q );
  63. return q;
  64. }
  65. query_ptr
  66. Query::getFixed( const track_ptr& track, const result_ptr& result )
  67. {
  68. query_ptr q = query_ptr( new Query( track, result ), &QObject::deleteLater );
  69. q->setWeakRef( q.toWeakRef() );
  70. return q;
  71. }
  72. Query::Query( const track_ptr& track, const QID& qid, bool autoResolve )
  73. : d_ptr( new QueryPrivate( this, track, qid ) )
  74. {
  75. init();
  76. if ( autoResolve )
  77. {
  78. connect( Database::instance(), SIGNAL( indexReady() ), SLOT( refreshResults() ), Qt::QueuedConnection );
  79. }
  80. connect( Pipeline::instance(), SIGNAL( resolverAdded( Tomahawk::Resolver* ) ), SLOT( onResolverAdded() ), Qt::QueuedConnection );
  81. }
  82. Query::Query( const track_ptr& track, const result_ptr& result )
  83. : d_ptr( new QueryPrivate( this, track, QString() ) )
  84. {
  85. Q_D( Query );
  86. init();
  87. d->allowReresolve = false;
  88. d->resolveFinished = true;
  89. d->results << result;
  90. d->playable = result->playable();
  91. d->solved = true;
  92. d->score = 1.0;
  93. connect( result.data(), SIGNAL( statusChanged() ), SLOT( onResultStatusChanged() ) );
  94. }
  95. Query::Query( const QString& query, const QID& qid )
  96. : d_ptr( new QueryPrivate( this, query, qid ) )
  97. {
  98. init();
  99. if ( !qid.isEmpty() )
  100. {
  101. connect( Database::instance(), SIGNAL( indexReady() ), SLOT( refreshResults() ), Qt::QueuedConnection );
  102. }
  103. }
  104. Query::~Query()
  105. {
  106. }
  107. void
  108. Query::init()
  109. {
  110. Q_D( Query );
  111. d->resolveFinished = false;
  112. d->solved = false;
  113. d->playable = false;
  114. d->saveResultHint = false;
  115. d->score = 0.0;
  116. }
  117. track_ptr
  118. Query::queryTrack() const
  119. {
  120. Q_D( const Query );
  121. return d->queryTrack;
  122. }
  123. track_ptr
  124. Query::track() const
  125. {
  126. Q_D( const Query );
  127. {
  128. QMutexLocker lock( &d->mutex );
  129. if ( !d->results.isEmpty() )
  130. return d->results.first()->track();
  131. }
  132. return d->queryTrack;
  133. }
  134. void
  135. Query::addResults( const QList< Tomahawk::result_ptr >& newresults )
  136. {
  137. Q_D( Query );
  138. {
  139. QMutexLocker lock( &d->mutex );
  140. /* const QStringList smt = AudioEngine::instance()->supportedMimeTypes();
  141. foreach ( const Tomahawk::result_ptr& result, newresults )
  142. {
  143. if ( !smt.contains( result->mimetype() ) )
  144. {
  145. tDebug() << "Won't accept result, unsupported mimetype" << result->toString() << result->mimetype();
  146. }
  147. else
  148. m_results.append( result );
  149. }*/
  150. d->results << newresults;
  151. sortResults();
  152. // hook up signals, and check solved status
  153. foreach( const result_ptr& rp, newresults )
  154. {
  155. connect( rp.data(), SIGNAL( statusChanged() ), SLOT( onResultStatusChanged() ) );
  156. }
  157. }
  158. checkResults();
  159. emit resultsAdded( newresults );
  160. emit resultsChanged();
  161. }
  162. void
  163. Query::addAlbums( const QList< Tomahawk::album_ptr >& newalbums )
  164. {
  165. {
  166. Q_D( Query );
  167. QMutexLocker lock( &d->mutex );
  168. d->albums << newalbums;
  169. }
  170. emit albumsAdded( newalbums );
  171. }
  172. void
  173. Query::addArtists( const QList< Tomahawk::artist_ptr >& newartists )
  174. {
  175. {
  176. Q_D( Query );
  177. QMutexLocker lock( &d->mutex );
  178. d->artists << newartists;
  179. }
  180. emit artistsAdded( newartists );
  181. }
  182. void
  183. Query::refreshResults()
  184. {
  185. Q_D( Query );
  186. clearResults();
  187. if ( d->resolveFinished && d->allowReresolve )
  188. {
  189. d->resolveFinished = false;
  190. query_ptr q = d->ownRef.toStrongRef();
  191. if ( q )
  192. Pipeline::instance()->resolve( q );
  193. }
  194. }
  195. void
  196. Query::onResultStatusChanged()
  197. {
  198. {
  199. Q_D( Query );
  200. QMutexLocker lock( &d->mutex );
  201. if ( !d->results.isEmpty() )
  202. sortResults();
  203. }
  204. checkResults();
  205. emit resultsChanged();
  206. }
  207. void
  208. Query::removeResult( const Tomahawk::result_ptr& result )
  209. {
  210. {
  211. Q_D( Query );
  212. QMutexLocker lock( &d->mutex );
  213. d->results.removeAll( result );
  214. if ( d->preferredResult == result )
  215. {
  216. d->preferredResult.clear();
  217. }
  218. sortResults();
  219. }
  220. emit resultsRemoved( result );
  221. checkResults();
  222. emit resultsChanged();
  223. }
  224. void
  225. Query::clearResults()
  226. {
  227. Q_D( Query );
  228. d->solved = false;
  229. d->playable = false;
  230. {
  231. QMutexLocker lock( &d->mutex );
  232. d->results.clear();
  233. }
  234. emit playableStateChanged( false );
  235. emit solvedStateChanged( false );
  236. emit resultsChanged();
  237. }
  238. void
  239. Query::onResolvingFinished()
  240. {
  241. Q_D( Query );
  242. tDebug( LOGVERBOSE ) << "Finished resolving:" << toString();
  243. if ( !d->resolveFinished )
  244. {
  245. d->resolveFinished = true;
  246. d->resolvers.clear();
  247. emit resolvingFinished( d->playable );
  248. }
  249. }
  250. void
  251. Query::onResolverAdded()
  252. {
  253. if ( !solved() )
  254. {
  255. refreshResults();
  256. }
  257. }
  258. QList< result_ptr >
  259. Query::results() const
  260. {
  261. Q_D( const Query );
  262. QMutexLocker lock( &d->mutex );
  263. return d->results;
  264. }
  265. unsigned int
  266. Query::numResults( bool onlyPlayableResults ) const
  267. {
  268. Q_D( const Query );
  269. QMutexLocker lock( &d->mutex );
  270. if ( onlyPlayableResults )
  271. {
  272. unsigned int c = 0;
  273. foreach ( const result_ptr& result, d->results )
  274. {
  275. if ( result->isOnline() )
  276. c++;
  277. }
  278. return c;
  279. }
  280. return d->results.length();
  281. }
  282. bool
  283. Query::resolvingFinished() const
  284. {
  285. Q_D( const Query );
  286. return d->resolveFinished;
  287. }
  288. bool
  289. Query::solved() const
  290. {
  291. Q_D( const Query );
  292. return d->solved;
  293. }
  294. bool
  295. Query::playable() const
  296. {
  297. Q_D( const Query );
  298. return d->playable;
  299. }
  300. QID
  301. Query::id() const
  302. {
  303. Q_D( const Query );
  304. if ( d->qid.isEmpty() )
  305. {
  306. d->qid = uuid();
  307. }
  308. return d->qid;
  309. }
  310. bool
  311. Query::resultSorter( const result_ptr& left, const result_ptr& right )
  312. {
  313. Q_D( Query );
  314. if ( !d->preferredResult.isNull() )
  315. {
  316. if ( d->preferredResult == left )
  317. return true;
  318. if ( d->preferredResult == right )
  319. return false;
  320. }
  321. const float ls = left->isOnline() ? howSimilar( left ) : 0.0;
  322. const float rs = right->isOnline() ? howSimilar( right ) : 0.0;
  323. if ( ls == rs )
  324. {
  325. if ( right->isLocal() )
  326. {
  327. return false;
  328. }
  329. if ( left->isPreview() != right->isPreview() )
  330. {
  331. return !left->isPreview();
  332. }
  333. if ( left->resolvedBy() != nullptr && right->resolvedBy() != nullptr )
  334. {
  335. return left->resolvedBy()->weight() > right->resolvedBy()->weight();
  336. }
  337. return left->id() > right->id();
  338. }
  339. if ( left->isPreview() != right->isPreview() )
  340. {
  341. return !left->isPreview();
  342. }
  343. return ls > rs;
  344. }
  345. result_ptr
  346. Query::preferredResult() const
  347. {
  348. Q_D( const Query );
  349. return d->preferredResult;
  350. }
  351. void
  352. Query::setPreferredResult( const result_ptr& result )
  353. {
  354. {
  355. Q_D( Query );
  356. QMutexLocker lock( &d->mutex );
  357. Q_ASSERT( d->results.contains( result ) );
  358. d->preferredResult = result;
  359. sortResults();
  360. }
  361. emit resultsChanged();
  362. }
  363. void
  364. Query::setCurrentResolver( Tomahawk::Resolver* resolver )
  365. {
  366. Q_D( Query );
  367. d->resolvers << resolver;
  368. }
  369. Tomahawk::Resolver*
  370. Query::currentResolver() const
  371. {
  372. Q_D( const Query );
  373. int x = d->resolvers.count();
  374. while ( --x )
  375. {
  376. QPointer< Resolver > r = d->resolvers.at( x );
  377. if ( r.isNull() )
  378. continue;
  379. return r.data();
  380. }
  381. return 0;
  382. }
  383. QList< QPointer<Resolver> >
  384. Query::resolvedBy() const
  385. {
  386. Q_D( const Query );
  387. return d->resolvers;
  388. }
  389. QString
  390. Query::fullTextQuery() const
  391. {
  392. Q_D( const Query );
  393. return d->fullTextQuery;
  394. }
  395. bool
  396. Query::isFullTextQuery() const
  397. {
  398. Q_D( const Query );
  399. return !d->fullTextQuery.isEmpty();
  400. }
  401. void
  402. Query::setResolveFinished( bool resolved )
  403. {
  404. Q_D( Query );
  405. d->resolveFinished = resolved;
  406. }
  407. void
  408. Query::allowReresolve()
  409. {
  410. Q_D( Query );
  411. d->allowReresolve = true;
  412. }
  413. void
  414. Query::disallowReresolve()
  415. {
  416. Q_D( Query );
  417. d->allowReresolve = false;
  418. }
  419. void
  420. Query::checkResults()
  421. {
  422. Q_D( Query );
  423. if ( !d->results.isEmpty() )
  424. {
  425. d->score = howSimilar( d->results.first() );
  426. }
  427. else
  428. {
  429. d->score = 0.0;
  430. }
  431. bool playable = false;
  432. bool solved = false;
  433. {
  434. QMutexLocker lock( &d->mutex );
  435. // hook up signals, and check solved status
  436. foreach( const result_ptr& rp, d->results )
  437. {
  438. if ( rp->playable() )
  439. playable = true;
  440. if ( rp->isOnline() && howSimilar( rp ) > 0.99 )
  441. {
  442. solved = true;
  443. }
  444. if ( playable )
  445. break;
  446. }
  447. }
  448. if ( d->solved && !solved )
  449. {
  450. refreshResults();
  451. }
  452. else
  453. {
  454. if ( d->playable != playable )
  455. {
  456. d->playable = playable;
  457. emit playableStateChanged( d->playable );
  458. }
  459. if ( d->solved != solved )
  460. {
  461. d->solved = solved;
  462. emit solvedStateChanged( d->solved );
  463. }
  464. }
  465. }
  466. bool
  467. Query::equals( const Tomahawk::query_ptr& other, bool ignoreCase, bool ignoreAlbum ) const
  468. {
  469. if ( !other )
  470. return false;
  471. if ( ignoreCase )
  472. {
  473. return ( queryTrack()->artist().toLower() == other->queryTrack()->artist().toLower() &&
  474. ( ignoreAlbum || queryTrack()->album().toLower() == other->queryTrack()->album().toLower() ) &&
  475. queryTrack()->track().toLower() == other->queryTrack()->track().toLower() );
  476. }
  477. return ( queryTrack()->artist() == other->queryTrack()->artist() &&
  478. ( ignoreAlbum || queryTrack()->album() == other->queryTrack()->album() ) &&
  479. queryTrack()->track() == other->queryTrack()->track() );
  480. }
  481. QVariant
  482. Query::toVariant() const
  483. {
  484. QVariantMap m;
  485. m.insert( "artist", queryTrack()->artist() );
  486. m.insert( "album", queryTrack()->album() );
  487. m.insert( "track", queryTrack()->track() );
  488. m.insert( "duration", queryTrack()->duration() );
  489. m.insert( "qid", id() );
  490. return m;
  491. }
  492. QString
  493. Query::toString() const
  494. {
  495. if ( !isFullTextQuery() )
  496. {
  497. return QString( "Query(%1, %2 - %3%4)" )
  498. .arg( id() )
  499. .arg( queryTrack()->artist() )
  500. .arg( queryTrack()->track() )
  501. .arg( queryTrack()->album().isEmpty() ? "" : QString( " on %1" ).arg( queryTrack()->album() ) );
  502. }
  503. else
  504. {
  505. return QString( "Query(%1, Fulltext: %2)" )
  506. .arg( id() )
  507. .arg( fullTextQuery() );
  508. }
  509. }
  510. float
  511. Query::score() const
  512. {
  513. Q_D( const Query );
  514. return d->score;
  515. }
  516. // TODO make clever (ft. featuring live (stuff) etc)
  517. float
  518. Query::howSimilar( const Tomahawk::result_ptr& r )
  519. {
  520. Q_D( Query );
  521. if (d->howSimilarCache.find(r->id()) != d->howSimilarCache.end())
  522. {
  523. return d->howSimilarCache[r->id()];
  524. }
  525. // result values
  526. const QString& rArtistname = r->track()->artistSortname();
  527. const QString& rAlbumname = r->track()->albumSortname();
  528. const QString& rTrackname = r->track()->trackSortname();
  529. QString qArtistname;
  530. QString qAlbumname;
  531. QString qTrackname;
  532. if ( isFullTextQuery() )
  533. {
  534. qArtistname = DatabaseImpl::sortname( d->fullTextQuery, true );
  535. qAlbumname = DatabaseImpl::sortname( d->fullTextQuery );
  536. qTrackname = qAlbumname;
  537. }
  538. else
  539. {
  540. qArtistname = queryTrack()->artistSortname();
  541. qAlbumname = queryTrack()->albumSortname();
  542. qTrackname = queryTrack()->trackSortname();
  543. }
  544. static const QRegExp filterOutChars = QRegExp(QString::fromUtf8("[-`´~!@#$%^&*()_—+=|:;<>«»,.?/{}\'\"\\[\\]\\\\]"));
  545. //Cleanup symbols for minor naming differences
  546. qArtistname.remove(filterOutChars);
  547. qTrackname.remove(filterOutChars);
  548. qAlbumname.remove(filterOutChars);
  549. // normal edit distance
  550. const int artdist = TomahawkUtils::levenshtein( qArtistname, rArtistname );
  551. const int trkdist = TomahawkUtils::levenshtein( qTrackname, rTrackname );
  552. // max length of name
  553. const int mlart = qMax( qArtistname.length(), rArtistname.length() );
  554. const int mltrk = qMax( qTrackname.length(), rTrackname.length() );
  555. // distance scores
  556. const float dcart = (float)( mlart - artdist ) / mlart;
  557. const float dctrk = (float)( mltrk - trkdist ) / mltrk;
  558. // don't penalize for missing album name
  559. float dcalb = 1.0;
  560. if ( !qAlbumname.isEmpty() )
  561. {
  562. const int albdist = TomahawkUtils::levenshtein( qAlbumname, rAlbumname );
  563. const int mlalb = qMax( qAlbumname.length(), rAlbumname.length() );
  564. dcalb = (float)( mlalb - albdist ) / mlalb;
  565. }
  566. if ( isFullTextQuery() )
  567. {
  568. const QString artistTrackname = DatabaseImpl::sortname( fullTextQuery() );
  569. const QString rArtistTrackname = DatabaseImpl::sortname( r->track()->artist() + " " + r->track()->track() );
  570. const int atrdist = TomahawkUtils::levenshtein( artistTrackname, rArtistTrackname );
  571. const int mlatr = qMax( artistTrackname.length(), rArtistTrackname.length() );
  572. const float dcatr = (float)( mlatr - atrdist ) / mlatr;
  573. float resultScore = qMax( dctrk, qMax( dcatr, qMax( dcart, dcalb ) ) );
  574. d->howSimilarCache[r->id()] = resultScore;
  575. return resultScore;
  576. }
  577. else
  578. {
  579. // weighted, so album match is worth less than track title
  580. float resultScore = ( dcart * 4 + dcalb + dctrk * 5 ) / 10;
  581. d->howSimilarCache[r->id()] = resultScore;
  582. return resultScore;
  583. }
  584. }
  585. void
  586. Query::setSaveHTTPResultHint( bool saveResultHint )
  587. {
  588. Q_D( Query );
  589. d->saveResultHint = saveResultHint;
  590. }
  591. bool
  592. Query::saveHTTPResultHint() const
  593. {
  594. Q_D( const Query );
  595. return d->saveResultHint;
  596. }
  597. QString
  598. Query::resultHint() const
  599. {
  600. Q_D( const Query );
  601. return d->resultHint;
  602. }
  603. void
  604. Query::setResultHint( const QString& resultHint )
  605. {
  606. Q_D( Query );
  607. d->resultHint = resultHint;
  608. }
  609. QWeakPointer<Query>
  610. Query::weakRef()
  611. {
  612. Q_D( Query );
  613. return d->ownRef;
  614. }
  615. void
  616. Query::setWeakRef( QWeakPointer<Query> weakRef )
  617. {
  618. Q_D( Query );
  619. d->ownRef = weakRef;
  620. }
  621. void
  622. Query::sortResults()
  623. {
  624. Q_D( Query );
  625. qStableSort( d->results.begin(), d->results.end(), std::bind( &Query::resultSorter, this, std::placeholders::_1, std::placeholders::_2 ) );
  626. }