/src/libtomahawk/database/DatabaseImpl.cpp

http://github.com/tomahawk-player/tomahawk · C++ · 816 lines · 630 code · 134 blank · 52 comment · 75 complexity · c6eacbc9b27a42ff75fb6b8c4ec36a13 MD5 · raw file

  1. /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
  2. *
  3. * Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
  4. * Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
  5. * Copyright 2010-2011, Jeff Mitchell <jeff@tomahawk-player.org>
  6. * Copyright 2014, Teo Mrnjavac <teo@kde.org>
  7. *
  8. * Tomahawk is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * Tomahawk is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. #include "DatabaseImpl.h"
  22. #include "database/Database.h"
  23. #include "utils/Logger.h"
  24. #include "utils/ResultUrlChecker.h"
  25. #include "utils/TomahawkUtils.h"
  26. #include "Album.h"
  27. #include "Artist.h"
  28. #include "fuzzyindex/DatabaseFuzzyIndex.h"
  29. #include "PlaylistEntry.h"
  30. #include "Result.h"
  31. #include "SourceList.h"
  32. #include "Track.h"
  33. #include <QtAlgorithms>
  34. #include <QCoreApplication>
  35. #include <QFile>
  36. #include <QRegExp>
  37. #include <QStringList>
  38. #include <QTime>
  39. #include <QTimer>
  40. /* !!!! You need to manually generate Schema.sql.h when the schema changes:
  41. cd src/libtomahawk/database
  42. ./gen_schema.h.sh ./Schema.sql tomahawk > Schema.sql.h
  43. */
  44. #include "Schema.sql.h"
  45. #define CURRENT_SCHEMA_VERSION 31
  46. Tomahawk::DatabaseImpl::DatabaseImpl( const QString& dbname )
  47. {
  48. QTime t;
  49. t.start();
  50. // Signals for splash screen must be connected here
  51. connect( this, SIGNAL( schemaUpdateStarted() ),
  52. qApp, SLOT( onSchemaUpdateStarted() ) );
  53. connect( this, SIGNAL( schemaUpdateStatus( QString ) ),
  54. qApp, SLOT( onSchemaUpdateStatus( QString ) ) );
  55. connect( this, SIGNAL( schemaUpdateDone() ),
  56. qApp, SLOT( onSchemaUpdateDone() ) );
  57. bool schemaUpdated = openDatabase( dbname );
  58. tDebug( LOGVERBOSE ) << "Opened database:" << t.elapsed();
  59. TomahawkSqlQuery query = newquery();
  60. query.exec( "SELECT v FROM settings WHERE k='dbid'" );
  61. if ( query.next() )
  62. {
  63. m_dbid = query.value( 0 ).toString();
  64. }
  65. else
  66. {
  67. m_dbid = uuid();
  68. query.exec( QString( "INSERT INTO settings(k,v) VALUES('dbid','%1')" ).arg( m_dbid ) );
  69. }
  70. tLog() << "Database ID:" << m_dbid;
  71. init();
  72. query.exec( "PRAGMA auto_vacuum = FULL" );
  73. query.exec( "PRAGMA synchronous = NORMAL" );
  74. tDebug( LOGVERBOSE ) << "Tweaked db pragmas:" << t.elapsed();
  75. // in case of unclean shutdown last time:
  76. query.exec( "UPDATE source SET isonline = 'false'" );
  77. query.exec( "DELETE FROM oplog WHERE source IS NULL AND singleton = 'true'" );
  78. m_fuzzyIndex = new Tomahawk::DatabaseFuzzyIndex( this, schemaUpdated );
  79. tDebug( LOGVERBOSE ) << "Loaded index:" << t.elapsed();
  80. if ( qApp->arguments().contains( "--dumpdb" ) )
  81. {
  82. dumpDatabase();
  83. ::exit( 0 );
  84. }
  85. }
  86. Tomahawk::DatabaseImpl::DatabaseImpl( const QString& dbname, bool internal )
  87. {
  88. Q_UNUSED( internal );
  89. openDatabase( dbname, false );
  90. init();
  91. }
  92. void
  93. Tomahawk::DatabaseImpl::init()
  94. {
  95. m_lastartid = m_lastalbid = m_lasttrkid = 0;
  96. TomahawkSqlQuery query = newquery();
  97. // make sqlite behave how we want:
  98. query.exec( "PRAGMA foreign_keys = ON" );
  99. }
  100. Tomahawk::DatabaseImpl::~DatabaseImpl()
  101. {
  102. tDebug() << "Shutting down database connection.";
  103. /*
  104. #ifdef TOMAHAWK_QUERY_ANALYZE
  105. TomahawkSqlQuery q = newquery();
  106. q.exec( "ANALYZE" );
  107. q.exec( "SELECT * FROM sqlite_stat1" );
  108. while ( q.next() )
  109. {
  110. tLog( LOGSQL ) << q.value( 0 ).toString() << q.value( 1 ).toString() << q.value( 2 ).toString();
  111. }
  112. #endif
  113. */
  114. }
  115. TomahawkSqlQuery
  116. Tomahawk::DatabaseImpl::newquery()
  117. {
  118. QMutexLocker lock( &m_mutex );
  119. return TomahawkSqlQuery( m_db );
  120. }
  121. QSqlDatabase&
  122. Tomahawk::DatabaseImpl::database()
  123. {
  124. QMutexLocker lock( &m_mutex );
  125. return m_db;
  126. }
  127. Tomahawk::DatabaseImpl*
  128. Tomahawk::DatabaseImpl::clone() const
  129. {
  130. QMutexLocker lock( &m_mutex );
  131. DatabaseImpl* impl = new DatabaseImpl( m_db.databaseName(), true );
  132. impl->setDatabaseID( m_dbid );
  133. impl->setFuzzyIndex( m_fuzzyIndex );
  134. return impl;
  135. }
  136. void
  137. Tomahawk::DatabaseImpl::dumpDatabase()
  138. {
  139. QFile dump( "dbdump.txt" );
  140. if ( !dump.open( QIODevice::WriteOnly | QIODevice::Text ) )
  141. {
  142. tDebug() << "Couldn't open dbdump.txt for writing!";
  143. Q_ASSERT( false );
  144. }
  145. else
  146. {
  147. QTextStream dumpout( &dump );
  148. TomahawkSqlQuery query = newquery();
  149. query.exec( "SELECT * FROM oplog" );
  150. while ( query.next() )
  151. {
  152. dumpout << "ID: " << query.value( 0 ).toInt() << endl
  153. << "GUID: " << query.value( 2 ).toString() << endl
  154. << "Command: " << query.value( 3 ).toString() << endl
  155. << "Singleton: " << query.value( 4 ).toBool() << endl
  156. << "JSON: " << ( query.value( 5 ).toBool() ? qUncompress( query.value( 6 ).toByteArray() ) : query.value( 6 ).toByteArray() )
  157. << endl << endl << endl;
  158. }
  159. }
  160. }
  161. void
  162. Tomahawk::DatabaseImpl::loadIndex()
  163. {
  164. connect( m_fuzzyIndex, SIGNAL( indexStarted() ), SIGNAL( indexStarted() ) );
  165. connect( m_fuzzyIndex, SIGNAL( indexReady() ), SIGNAL( indexReady() ) );
  166. m_fuzzyIndex->loadLuceneIndex();
  167. }
  168. bool
  169. Tomahawk::DatabaseImpl::updateSchema( int oldVersion )
  170. {
  171. // we are called here with the old database. we must migrate it to the CURRENT_SCHEMA_VERSION from the oldVersion
  172. if ( oldVersion == 0 ) // empty database, so create our tables and stuff
  173. {
  174. tLog() << "Create tables... old version is" << oldVersion;
  175. QString sql( get_tomahawk_sql() );
  176. QStringList statements = sql.split( ";", QString::SkipEmptyParts );
  177. m_db.transaction();
  178. foreach ( const QString& sl, statements )
  179. {
  180. QString s( sl.trimmed() );
  181. if ( s.isEmpty() )
  182. continue;
  183. tLog() << "Executing:" << s;
  184. TomahawkSqlQuery query = newquery();
  185. query.exec( s );
  186. }
  187. m_db.commit();
  188. return true;
  189. }
  190. else // update in place! run the proper upgrade script
  191. {
  192. emit schemaUpdateStarted();
  193. int cur = oldVersion;
  194. m_db.transaction();
  195. while ( cur < CURRENT_SCHEMA_VERSION )
  196. {
  197. cur++;
  198. QString path = QString( RESPATH "sql/dbmigrate-%1_to_%2.sql" ).arg( cur - 1 ).arg( cur );
  199. QFile script( path );
  200. if ( !script.exists() || !script.open( QIODevice::ReadOnly ) )
  201. {
  202. tLog() << "Failed to find or open upgrade script from" << (cur-1) << "to" << cur << " (" << path << ")! Aborting upgrade...";
  203. return false;
  204. }
  205. QString sql = QString::fromUtf8( script.readAll() ).trimmed();
  206. QStringList statements = sql.split( ";", QString::SkipEmptyParts );
  207. for ( int i = 0; i < statements.count(); ++i )
  208. {
  209. QString sql = statements.at( i );
  210. QString clean = cleanSql( sql ).trimmed();
  211. if ( clean.isEmpty() )
  212. continue;
  213. tLog() << "Executing upgrade statement:" << clean;
  214. TomahawkSqlQuery q = newquery();
  215. q.exec( clean );
  216. //Report to splash screen
  217. emit schemaUpdateStatus( QString( "%1/%2" ).arg( QString::number( i + 1 ) )
  218. .arg( QString::number( statements.count() ) ) );
  219. }
  220. }
  221. m_db.commit();
  222. tLog() << "DB Upgrade successful!";
  223. emit schemaUpdateDone();
  224. return true;
  225. }
  226. }
  227. QString
  228. Tomahawk::DatabaseImpl::cleanSql( const QString& sql )
  229. {
  230. QString fixed = sql;
  231. QRegExp r( "--[^\\n]*" );
  232. fixed.replace( r, QString() );
  233. return fixed.trimmed();
  234. }
  235. Tomahawk::result_ptr
  236. Tomahawk::DatabaseImpl::file( int fid )
  237. {
  238. Tomahawk::result_ptr r;
  239. TomahawkSqlQuery query = newquery();
  240. query.exec( QString( "SELECT url, mtime, size, md5, mimetype, duration, bitrate, "
  241. "file_join.artist, file_join.album, file_join.track, file_join.composer, "
  242. "(SELECT name FROM artist WHERE id = file_join.artist) AS artname, "
  243. "(SELECT name FROM album WHERE id = file_join.album) AS albname, "
  244. "(SELECT name FROM track WHERE id = file_join.track) AS trkname, "
  245. "(SELECT name FROM artist WHERE id = file_join.composer) AS cmpname, "
  246. "source, "
  247. "(SELECT artist.name FROM artist, album WHERE artist.id = album.artist AND album.id = file_join.album) AS albumartname "
  248. "FROM file, file_join "
  249. "WHERE file.id = file_join.file AND file.id = %1" )
  250. .arg( fid ) );
  251. if ( query.next() )
  252. {
  253. QString url = query.value( 0 ).toString();
  254. Tomahawk::source_ptr s = SourceList::instance()->get( query.value( 15 ).toUInt() );
  255. if ( !s )
  256. return r;
  257. if ( !s->isLocal() )
  258. url = QString( "servent://%1\t%2" ).arg( s->nodeId() ).arg( url );
  259. Tomahawk::track_ptr track = Tomahawk::Track::get( query.value( 9 ).toUInt(), query.value( 11 ).toString(), query.value( 13 ).toString(),
  260. query.value( 12 ).toString(), query.value( 16 ).toString(), query.value( 5 ).toUInt(),
  261. query.value( 14 ).toString(), 0, 0 );
  262. if ( !track )
  263. return r;
  264. r = Tomahawk::Result::get( url, track );
  265. if ( !r )
  266. return r;
  267. r->setModificationTime( query.value( 1 ).toUInt() );
  268. r->setSize( query.value( 2 ).toUInt() );
  269. r->setMimetype( query.value( 4 ).toString() );
  270. r->setBitrate( query.value( 6 ).toUInt() );
  271. r->setResolvedByCollection( s->dbCollection() );
  272. r->setFileId( fid );
  273. }
  274. return r;
  275. }
  276. int
  277. Tomahawk::DatabaseImpl::artistId( const QString& name_orig, bool autoCreate )
  278. {
  279. if ( m_lastart == name_orig )
  280. return m_lastartid;
  281. int id = 0;
  282. QString sortname = Tomahawk::DatabaseImpl::sortname( name_orig );
  283. TomahawkSqlQuery query = newquery();
  284. query.prepare( "SELECT id FROM artist WHERE sortname = ?" );
  285. query.addBindValue( sortname );
  286. query.exec();
  287. if ( query.next() )
  288. {
  289. id = query.value( 0 ).toInt();
  290. }
  291. if ( id )
  292. {
  293. m_lastart = name_orig;
  294. m_lastartid = id;
  295. return id;
  296. }
  297. if ( autoCreate )
  298. {
  299. // not found, insert it.
  300. query.prepare( "INSERT INTO artist(id,name,sortname) VALUES(NULL,?,?)" );
  301. query.addBindValue( name_orig );
  302. query.addBindValue( sortname );
  303. if ( !query.exec() )
  304. {
  305. tDebug() << "Failed to insert artist:" << name_orig;
  306. return 0;
  307. }
  308. id = query.lastInsertId().toInt();
  309. m_lastart = name_orig;
  310. m_lastartid = id;
  311. }
  312. return id;
  313. }
  314. int
  315. Tomahawk::DatabaseImpl::trackId( int artistid, const QString& name_orig, bool autoCreate )
  316. {
  317. int id = 0;
  318. QString sortname = Tomahawk::DatabaseImpl::sortname( name_orig );
  319. //if( ( id = m_artistcache[sortname] ) ) return id;
  320. TomahawkSqlQuery query = newquery();
  321. query.prepare( "SELECT id FROM track WHERE artist = ? AND sortname = ?" );
  322. query.addBindValue( artistid );
  323. query.addBindValue( sortname );
  324. query.exec();
  325. if ( query.next() )
  326. {
  327. id = query.value( 0 ).toInt();
  328. }
  329. if ( id )
  330. {
  331. //m_trackcache[sortname]=id;
  332. return id;
  333. }
  334. if ( autoCreate )
  335. {
  336. // not found, insert it.
  337. query.prepare( "INSERT INTO track(id,artist,name,sortname) VALUES(NULL,?,?,?)" );
  338. query.addBindValue( artistid );
  339. query.addBindValue( name_orig );
  340. query.addBindValue( sortname );
  341. if ( !query.exec() )
  342. {
  343. tDebug() << "Failed to insert track:" << name_orig;
  344. return 0;
  345. }
  346. id = query.lastInsertId().toInt();
  347. }
  348. return id;
  349. }
  350. int
  351. Tomahawk::DatabaseImpl::albumId( int artistid, const QString& name_orig, bool autoCreate )
  352. {
  353. if ( name_orig.isEmpty() )
  354. {
  355. //qDebug() << Q_FUNC_INFO << "empty album name";
  356. return 0;
  357. }
  358. if ( m_lastartid == artistid && m_lastalb == name_orig )
  359. return m_lastalbid;
  360. int id = 0;
  361. QString sortname = Tomahawk::DatabaseImpl::sortname( name_orig );
  362. //if( ( id = m_albumcache[sortname] ) ) return id;
  363. TomahawkSqlQuery query = newquery();
  364. query.prepare( "SELECT id FROM album WHERE artist = ? AND sortname = ?" );
  365. query.addBindValue( artistid );
  366. query.addBindValue( sortname );
  367. query.exec();
  368. if ( query.next() )
  369. {
  370. id = query.value( 0 ).toInt();
  371. }
  372. if ( id )
  373. {
  374. m_lastalb = name_orig;
  375. m_lastalbid = id;
  376. return id;
  377. }
  378. if ( autoCreate )
  379. {
  380. // not found, insert it.
  381. query.prepare( "INSERT INTO album(id,artist,name,sortname) VALUES(NULL,?,?,?)" );
  382. query.addBindValue( artistid );
  383. query.addBindValue( name_orig );
  384. query.addBindValue( sortname );
  385. if( !query.exec() )
  386. {
  387. tDebug() << "Failed to insert album:" << name_orig;
  388. return 0;
  389. }
  390. id = query.lastInsertId().toInt();
  391. m_lastalb = name_orig;
  392. m_lastalbid = id;
  393. }
  394. return id;
  395. }
  396. QList< QPair<int, float> >
  397. Tomahawk::DatabaseImpl::search( const Tomahawk::query_ptr& query, uint limit )
  398. {
  399. QList< QPair<int, float> > resultslist;
  400. QMap< int, float > resultsmap = m_fuzzyIndex->search( query );
  401. foreach ( int i, resultsmap.keys() )
  402. {
  403. resultslist << QPair<int, float>( i, (float)resultsmap.value( i ) );
  404. }
  405. qSort( resultslist.begin(), resultslist.end(), Tomahawk::DatabaseImpl::scorepairSorter );
  406. if ( !limit )
  407. return resultslist;
  408. QList< QPair<int, float> > resultscapped;
  409. for ( int i = 0; i < (int)limit && i < resultsmap.count(); i++ )
  410. {
  411. resultscapped << resultslist.at( i );
  412. }
  413. return resultscapped;
  414. }
  415. QList< QPair<int, float> >
  416. Tomahawk::DatabaseImpl::searchAlbum( const Tomahawk::query_ptr& query, uint limit )
  417. {
  418. QList< QPair<int, float> > resultslist;
  419. QMap< int, float > resultsmap = m_fuzzyIndex->searchAlbum( query );
  420. foreach ( int i, resultsmap.keys() )
  421. {
  422. resultslist << QPair<int, float>( i, (float)resultsmap.value( i ) );
  423. }
  424. qSort( resultslist.begin(), resultslist.end(), Tomahawk::DatabaseImpl::scorepairSorter );
  425. if ( !limit )
  426. return resultslist;
  427. QList< QPair<int, float> > resultscapped;
  428. for ( int i = 0; i < (int)limit && i < resultsmap.count(); i++ )
  429. {
  430. resultscapped << resultslist.at( i );
  431. }
  432. return resultscapped;
  433. }
  434. QList< int >
  435. Tomahawk::DatabaseImpl::getTrackFids( int tid )
  436. {
  437. QList< int > ret;
  438. TomahawkSqlQuery query = newquery();
  439. query.exec( QString( "SELECT file.id FROM file, file_join "
  440. "WHERE file_join.file=file.id "
  441. "AND file_join.track = %1 ").arg( tid ) );
  442. query.exec();
  443. while( query.next() )
  444. ret.append( query.value( 0 ).toInt() );
  445. return ret;
  446. }
  447. QString
  448. Tomahawk::DatabaseImpl::sortname( const QString& str, bool replaceArticle )
  449. {
  450. QString s = str.simplified().toLower();
  451. if ( replaceArticle && s.startsWith( "the " ) )
  452. {
  453. s = s.mid( 4 );
  454. }
  455. return s;
  456. }
  457. QVariantMap
  458. Tomahawk::DatabaseImpl::artist( int id )
  459. {
  460. TomahawkSqlQuery query = newquery();
  461. query.exec( QString( "SELECT id, name, sortname FROM artist WHERE id = %1" ).arg( id ) );
  462. QVariantMap m;
  463. if( !query.next() )
  464. return m;
  465. m["id"] = query.value( 0 );
  466. m["name"] = query.value( 1 );
  467. m["sortname"] = query.value( 2 );
  468. return m;
  469. }
  470. QVariantMap
  471. Tomahawk::DatabaseImpl::track( int id )
  472. {
  473. TomahawkSqlQuery query = newquery();
  474. query.exec( QString( "SELECT id, artist, name, sortname FROM track WHERE id = %1" ).arg( id ) );
  475. QVariantMap m;
  476. if( !query.next() )
  477. return m;
  478. m["id"] = query.value( 0 );
  479. m["artist"] = query.value( 1 );
  480. m["name"] = query.value( 2 );
  481. m["sortname"] = query.value( 3 );
  482. return m;
  483. }
  484. QVariantMap
  485. Tomahawk::DatabaseImpl::album( int id )
  486. {
  487. TomahawkSqlQuery query = newquery();
  488. query.exec( QString( "SELECT id, artist, name, sortname FROM album WHERE id = %1" ).arg( id ) );
  489. QVariantMap m;
  490. if( !query.next() )
  491. return m;
  492. m["id"] = query.value( 0 );
  493. m["artist"] = query.value( 1 );
  494. m["name"] = query.value( 2 );
  495. m["sortname"] = query.value( 3 );
  496. return m;
  497. }
  498. Tomahawk::result_ptr
  499. Tomahawk::DatabaseImpl::resultFromHint( const Tomahawk::query_ptr& origquery )
  500. {
  501. QString url = origquery->resultHint();
  502. TomahawkSqlQuery query = newquery();
  503. Tomahawk::source_ptr s;
  504. Tomahawk::result_ptr res;
  505. QString fileUrl;
  506. if ( url.contains( "servent://" ) )
  507. {
  508. QStringList parts = url.mid( QString( "servent://" ).length() ).split( "\t" );
  509. s = SourceList::instance()->get( parts.at( 0 ) );
  510. fileUrl = parts.at( 1 );
  511. if ( s.isNull() )
  512. return res;
  513. }
  514. else if ( url.contains( "file://" ) )
  515. {
  516. s = SourceList::instance()->getLocal();
  517. fileUrl = url;
  518. }
  519. else if ( TomahawkUtils::whitelistedHttpResultHint( url ) )
  520. {
  521. Tomahawk::track_ptr track = Tomahawk::Track::get( origquery->queryTrack()->artist(),
  522. origquery->queryTrack()->track(),
  523. origquery->queryTrack()->album(),
  524. QString(),
  525. origquery->queryTrack()->duration() );
  526. // Return http resulthint directly
  527. res = Tomahawk::Result::get( url, track );
  528. res->setRID( uuid() );
  529. const QUrl u = QUrl::fromUserInput( url );
  530. res->setFriendlySource( u.host() );
  531. ResultUrlChecker* checker = new ResultUrlChecker( origquery, nullptr, QList< result_ptr >() << res );
  532. QEventLoop loop;
  533. connect( checker, SIGNAL( done() ), &loop, SLOT( quit() ) );
  534. loop.exec();
  535. checker->deleteLater();
  536. if ( checker->validResults().isEmpty() )
  537. res = result_ptr();
  538. return res;
  539. }
  540. else
  541. {
  542. // No resulthint
  543. return res;
  544. }
  545. bool searchlocal = s->isLocal();
  546. QString sql = QString( "SELECT "
  547. "url, mtime, size, md5, mimetype, duration, bitrate, " //0
  548. "file_join.artist, file_join.album, file_join.track, " //7
  549. "file_join.composer, " //10
  550. "artist.name as artname, " //11
  551. "album.name as albname, " //12
  552. "track.name as trkname, " //13
  553. "composer.name as cmpname, " //14
  554. "file.source, " //15
  555. "file_join.albumpos, " //16
  556. "file_join.discnumber, " //17
  557. "artist.id as artid, " //18
  558. "album.id as albid, " //19
  559. "composer.id as cmpid, " //20
  560. "albumArtist.id as albumartistid, " //21
  561. "albumArtist.name as albumartistname " //22
  562. "FROM file, file_join, artist, track "
  563. "LEFT JOIN album ON album.id = file_join.album "
  564. "LEFT JOIN artist AS composer on composer.id = file_join.composer "
  565. "LEFT JOIN artist AS albumArtist on albumArtist.id = album.artist "
  566. "WHERE "
  567. "artist.id = file_join.artist AND "
  568. "track.id = file_join.track AND "
  569. "file.source %1 AND "
  570. "file_join.file = file.id AND "
  571. "file.url = ?"
  572. ).arg( searchlocal ? "IS NULL" : QString( "= %1" ).arg( s->id() ) );
  573. query.prepare( sql );
  574. query.bindValue( 0, fileUrl );
  575. query.exec();
  576. if ( query.next() )
  577. {
  578. QString url = query.value( 0 ).toString();
  579. Tomahawk::source_ptr s = SourceList::instance()->get( query.value( 15 ).toUInt() );
  580. if ( !s )
  581. return res;
  582. if ( !s->isLocal() )
  583. url = QString( "servent://%1\t%2" ).arg( s->nodeId() ).arg( url );
  584. Tomahawk::track_ptr track = Tomahawk::Track::get( query.value( 9 ).toUInt(),
  585. query.value( 11 ).toString(),
  586. query.value( 13 ).toString(),
  587. query.value( 12 ).toString(),
  588. query.value( 22 ).toString(),
  589. query.value( 5 ).toInt(),
  590. query.value( 14 ).toString(),
  591. query.value( 16 ).toUInt(),
  592. query.value( 17 ).toUInt() );
  593. track->loadAttributes();
  594. res = Tomahawk::Result::get( url, track );
  595. res->setModificationTime( query.value( 1 ).toUInt() );
  596. res->setSize( query.value( 2 ).toUInt() );
  597. res->setMimetype( query.value( 4 ).toString() );
  598. res->setBitrate( query.value( 6 ).toInt() );
  599. res->setRID( uuid() );
  600. res->setResolvedByCollection( s->dbCollection() );
  601. }
  602. return res;
  603. }
  604. bool
  605. Tomahawk::DatabaseImpl::openDatabase( const QString& dbname, bool checkSchema )
  606. {
  607. QString connName( "tomahawk" );
  608. if ( !checkSchema )
  609. {
  610. // secondary connection, use a unique connection name
  611. connName += "_" + uuid();
  612. }
  613. static QString sqlDriver;
  614. bool schemaUpdated = false;
  615. int version = -1;
  616. {
  617. if ( sqlDriver.isEmpty() )
  618. {
  619. QStringList drivers = QSqlDatabase::drivers();
  620. if (drivers.contains( "QSQLITE3" ))
  621. {
  622. sqlDriver = "QSQLITE3";
  623. }
  624. else
  625. {
  626. sqlDriver = "QSQLITE";
  627. }
  628. }
  629. QSqlDatabase db = QSqlDatabase::addDatabase( sqlDriver, connName );
  630. db.setDatabaseName( dbname );
  631. db.setConnectOptions( "QSQLITE_ENABLE_SHARED_CACHE=1" );
  632. if ( !db.open() )
  633. {
  634. tLog() << "Failed to open database" << dbname << "with driver" << sqlDriver;
  635. throw "failed to open db"; // TODO
  636. }
  637. if ( checkSchema )
  638. {
  639. QSqlQuery qry = QSqlQuery( db );
  640. qry.exec( "SELECT v FROM settings WHERE k='schema_version'" );
  641. if ( qry.next() )
  642. {
  643. version = qry.value( 0 ).toInt();
  644. tLog() << "Database schema of" << dbname << sqlDriver << "is" << version;
  645. }
  646. }
  647. else
  648. version = CURRENT_SCHEMA_VERSION;
  649. if ( version < 0 || version == CURRENT_SCHEMA_VERSION )
  650. m_db = db;
  651. }
  652. if ( version > 0 && version != CURRENT_SCHEMA_VERSION )
  653. {
  654. QSqlDatabase::removeDatabase( connName );
  655. QString newname = QString( "%1.v%2" ).arg( dbname ).arg( version );
  656. tLog() << endl << "****************************" << endl;
  657. tLog() << "Schema version too old: " << version << ". Current version is:" << CURRENT_SCHEMA_VERSION;
  658. tLog() << "Moving" << dbname << newname;
  659. tLog() << "If the migration fails, you can recover your DB by copying" << newname << "back to" << dbname;
  660. tLog() << endl << "****************************" << endl;
  661. QFile::copy( dbname, newname );
  662. {
  663. m_db = QSqlDatabase::addDatabase( sqlDriver, connName );
  664. m_db.setDatabaseName( dbname );
  665. if ( !m_db.open() )
  666. throw "db moving failed";
  667. schemaUpdated = updateSchema( version );
  668. if ( !schemaUpdated )
  669. {
  670. Q_ASSERT( false );
  671. QTimer::singleShot( 0, qApp, SLOT( quit() ) );
  672. }
  673. }
  674. }
  675. else if ( version < 0 )
  676. {
  677. schemaUpdated = updateSchema( 0 );
  678. }
  679. return schemaUpdated;
  680. }