1 /* DatabaseConnector.cpp */
2 
3 /* Copyright (C) 2011-2020 Michael Lugmair (Lucio Carreras)
4  *
5  * This file is part of sayonara player
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General 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  * This program 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 License for more details.
16 
17  * You should have received a copy of the GNU General License
18  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "Database/Connector.h"
22 #include "Database/Query.h"
23 #include "Database/LibraryDatabase.h"
24 #include "Database/Bookmarks.h"
25 #include "Database/Equalizer.h"
26 #include "Database/Playlist.h"
27 #include "Database/Podcasts.h"
28 #include "Database/Streams.h"
29 #include "Database/Session.h"
30 #include "Database/Settings.h"
31 #include "Database/Shortcuts.h"
32 #include "Database/VisualStyles.h"
33 #include "Database/CoverConnector.h"
34 
35 #include "Utils/MetaData/Album.h"
36 #include "Utils/MetaData/Artist.h"
37 #include "Utils/MetaData/MetaDataList.h"
38 #include "Utils/Logger/Logger.h"
39 #include "Utils/Utils.h"
40 #include "Utils/StandardPaths.h"
41 #include "Utils/Algorithm.h"
42 #include "Utils/RawShortcutMap.h"
43 
44 #include <QFileInfo>
45 #include <QDateTime>
46 #include <QTime>
47 
48 #include <tuple>
49 #include <algorithm>
50 
51 using DB::Connector;
52 using DB::LibraryDatabase;
53 
54 using LibDbIterator=DB::LibraryDatabases::Iterator;
55 namespace Algorithm=Util::Algorithm;
56 
highestDatabaseVersion()57 int Connector::highestDatabaseVersion()
58 {
59 	return 29;
60 }
61 
62 struct Connector::Private
63 {
64 	QString					connection_name;
65 	QString					defaultSourcedirectory;
66 	QString					defaultTargetdirectory;
67 	QString					defaultDatabsefilename;
68 
69 	DB::Bookmarks*			bookmarkConnector=nullptr;
70 	DB::Equalizer*          equalizerConnector=nullptr;
71 	DB::Playlist*			playlistConnector=nullptr;
72 	DB::Podcasts*			podcastConnector=nullptr;
73 	DB::Streams*			streamConnector=nullptr;
74 	DB::VisualStyles*		visualStyleConnector=nullptr;
75 	DB::Session*			sessionConnector=nullptr;
76 	DB::Settings*			settingsConnector=nullptr;
77 	DB::Shortcuts*			shortcutConnector=nullptr;
78 	DB::Covers*				coverConnector=nullptr;
79 	DB::Library*			libraryConnector=nullptr;
80 
81 	QList<LibraryDatabase*> libraryDbs;
82 	LibraryDatabase*		genericLibraryDatabase=nullptr;
83 
84 	int						oldDbVersion;
85 
86 
PrivateConnector::Private87 	Private() : oldDbVersion(0) {}
~PrivateConnector::Private88 	~Private()
89 	{
90 		if(bookmarkConnector){
91 			delete bookmarkConnector; bookmarkConnector = nullptr;
92 		}
93 
94 		if(equalizerConnector){
95 			delete equalizerConnector; equalizerConnector = nullptr;
96 		}
97 
98 		if(podcastConnector){
99 			delete podcastConnector; podcastConnector = nullptr;
100 		}
101 
102 		if(streamConnector){
103 			delete streamConnector; streamConnector = nullptr;
104 		}
105 
106 		if(visualStyleConnector){
107 			delete visualStyleConnector; visualStyleConnector = nullptr;
108 		}
109 
110 		if(settingsConnector){
111 			delete settingsConnector; settingsConnector = nullptr;
112 		}
113 
114 		if(shortcutConnector){
115 			delete shortcutConnector; shortcutConnector = nullptr;
116 		}
117 
118 		if(coverConnector){
119 			delete coverConnector; coverConnector = nullptr;
120 		}
121 
122 		if(libraryConnector){
123 			delete libraryConnector; libraryConnector = nullptr;
124 		}
125 
126 		if(sessionConnector){
127 			delete sessionConnector; sessionConnector = nullptr;
128 		}
129 	}
130 };
131 
132 class DatabaseNotCreatedException : public std::exception
133 {
134 public:
135 	const char* what() const noexcept;
136 };
137 
138 
Connector(const QString & sourceDirectory,const QString & targetDirectory,const QString & databseFilename)139 Connector::Connector(const QString& sourceDirectory, const QString& targetDirectory, const QString& databseFilename) :
140 	DB::Base(0, sourceDirectory, targetDirectory, databseFilename, nullptr)
141 {
142 	m = Pimpl::make<Private>();
143 
144 	if(!this->isInitialized()){
145 		throw DatabaseNotCreatedException();
146 	}
147 
148 	else
149 	{
150 		m->genericLibraryDatabase = new LibraryDatabase(connectionName(), databaseId(), -1);
151 		m->libraryDbs << m->genericLibraryDatabase;
152 
153 		applyFixes();
154 	}
155 }
156 
157 Connector::~Connector() = default;
158 
instance()159 DB::Connector* Connector::instance()
160 {
161 	return instance_custom(QString(), QString(), QString());
162 }
163 
instance_custom(QString sourceDirectory,QString targetDirectory,QString databseFilename)164 DB::Connector* Connector::instance_custom(QString sourceDirectory, QString targetDirectory, QString databseFilename)
165 {
166 	if(sourceDirectory.isEmpty()) {
167 		sourceDirectory = ":/Database";
168 	}
169 
170 	if(targetDirectory.isEmpty()) {
171 		targetDirectory = Util::xdgConfigPath();
172 	}
173 
174 	if(databseFilename.isEmpty()) {
175 		databseFilename = "player.db";
176 	}
177 
178 	static Connector connector(sourceDirectory, targetDirectory, databseFilename);
179 	return &connector;
180 }
181 
182 
updateAlbumCissearchFix()183 bool Connector::updateAlbumCissearchFix()
184 {
185 #ifdef DEBUG_DB
186 	sp_log(Log::Debug, this) << Q_FUNC_INFO;
187 #endif
188 
189 	AlbumList albums;
190 
191 	LibraryDatabase* lib_db = libraryDatabase(-1, 0);
192 	lib_db->getAllAlbums(albums, true);
193 
194 	for(const Album& album : albums)
195 	{
196 		QString str = "UPDATE albums SET cissearch=:cissearch WHERE albumID=:id;";
197 		Query q(this);
198 		q.prepare(str);
199 		q.bindValue(":cissearch",	Util::convertNotNull(album.name().toLower()));
200 		q.bindValue(":id",			album.id());
201 
202 		if(!q.exec()){
203 			q.showError("Cannot update album cissearch");
204 		}
205 	}
206 
207 	return true;
208 }
209 
updateArtistCissearchFix()210 bool Connector::updateArtistCissearchFix()
211 {
212 	ArtistList artists;
213 	LibraryDatabase* lib_db = libraryDatabase(-1, 0);
214 	lib_db->getAllArtists(artists, true);
215 	for(const Artist& artist : artists)
216 	{
217 		QString str =
218 				"UPDATE artists SET cissearch=:cissearch WHERE artistID=:id;";
219 
220 		Query q(this);
221 		q.prepare(str);
222 		q.bindValue(":cissearch",	Util::convertNotNull(artist.name().toLower()));
223 		q.bindValue(":id",			artist.id());
224 
225 		if(!q.exec()){
226 			q.showError("Cannot update artist cissearch");
227 		}
228 	}
229 
230 	return true;
231 }
232 
updateTrackCissearchFix()233 bool Connector::updateTrackCissearchFix()
234 {
235 	MetaDataList v_md;
236 	LibraryDatabase* lib_db = libraryDatabase(-1, 0);
237 	lib_db->getAllTracks(v_md);
238 	for(const MetaData& md : v_md) {
239 		lib_db->updateTrack(md);
240 	}
241 
242 	return true;
243 }
244 
updateLostArtists()245 bool Connector::updateLostArtists()
246 {
247 	LibraryDatabase* lib_db = libraryDatabase(-1, 0);
248 	if(!lib_db){
249 		spLog(Log::Error, this) << "Cannot find Library";
250 		return false;
251 	}
252 
253 	ArtistId id = lib_db->insertArtistIntoDatabase(QString());
254 
255 	const QStringList queries {
256 		QString("UPDATE tracks SET artistID=:artistID WHERE artistID IN (SELECT artistID FROM artists WHERE name IS NULL);"),
257 		QString("UPDATE tracks SET artistID=:artistID WHERE artistID NOT IN (SELECT artistID FROM artists);"),
258 		QString("UPDATE tracks SET albumArtistID=:artistID WHERE albumArtistID IN (SELECT artistID FROM artists WHERE name IS NULL);"),
259 		QString("UPDATE tracks SET albumArtistID=:artistID WHERE albumArtistID NOT IN (SELECT artistID FROM artists);"),
260 		QString("DELETE FROM artists WHERE name IS NULL;")
261 	};
262 
263 	this->transaction();
264 	for(const QString& query : queries)
265 	{
266 		DB::Query q(this);
267 		q.prepare(query);
268 		q.bindValue(":artistID", id);
269 		bool success = q.exec();
270 		if(!success){
271 			this->rollback();
272 			return false;
273 		}
274 	}
275 
276 	this->commit();
277 	return true;
278 }
279 
updateLostAlbums()280 bool Connector::updateLostAlbums()
281 {
282 	LibraryDatabase* libraryDatabase = this->libraryDatabase(-1, 0);
283 	if(!libraryDatabase) {
284 		spLog(Log::Error, this) << "Cannot find Library database";
285 		return false;
286 	}
287 
288 	return libraryDatabase->fixEmptyAlbums();
289 }
290 
oldDatabaseVersion() const291 int Connector::oldDatabaseVersion() const
292 {
293 	return m->oldDbVersion;
294 }
295 
applyFixes()296 bool Connector::applyFixes()
297 {
298 	QString versionString;
299 	int version;
300 	bool success;
301 	const int LatestVersion = highestDatabaseVersion();
302 
303 	success = settingsConnector()->loadSetting("version", versionString);
304 	version = versionString.toInt(&success);
305 	m->oldDbVersion = version;
306 
307 	spLog(Log::Info, this)
308 			<< "Database Version:  " << version << ". "
309 			<< "Latest Version: " << LatestVersion;
310 
311 	if(version == LatestVersion) {
312 		spLog(Log::Info, this) << "No need to update db";
313 		return true;
314 	}
315 
316 	else if(!success){
317 		 spLog(Log::Warning, this) << "Cannot get database version";
318 	}
319 
320 	settingsConnector()->loadSettings();
321 
322 	spLog(Log::Info, this) << "Apply fixes";
323 
324 	if(version < 1)
325 	{
326 		checkAndInsertColumn("playlisttotracks", "position", "INTEGER");
327 		checkAndInsertColumn("playlisttotracks", "filepath", "VARCHAR(512)");
328 		checkAndInsertColumn("tracks", "genre", "VARCHAR(1024)");
329 
330 		QString create_savedstreams = QString("CREATE TABLE savedstreams ") +
331 				"( " +
332 				"	name VARCHAR(255) PRIMARY KEY, " +
333 				"	url VARCHAR(255) " +
334 				");";
335 
336 		checkAndCreateTable("savedstreams", create_savedstreams);
337 
338 
339 		QString create_savedpodcasts = QString("CREATE TABLE savedpodcasts ") +
340 				"( " +
341 				"	name VARCHAR(255) PRIMARY KEY, " +
342 				"	url VARCHAR(255) " +
343 				");";
344 
345 		checkAndCreateTable("savedpodcasts", create_savedpodcasts);
346 	}
347 
348 	if(version < 3)
349 	{
350 		db().transaction();
351 
352 		bool success = true;
353 		success &= checkAndInsertColumn("tracks", "cissearch", "VARCHAR(512)");
354 		success &= checkAndInsertColumn("albums", "cissearch", "VARCHAR(512)");
355 		success &= checkAndInsertColumn("artists", "cissearch", "VARCHAR(512)");
356 
357 		Q_UNUSED(success)
358 
359 		updateAlbumCissearchFix();
360 		updateArtistCissearchFix();
361 		updateTrackCissearchFix();
362 
363 		db().commit();
364 	}
365 
366 
367 	if(version == 3) {
368 		checkAndDropTable("VisualStyles");
369 	}
370 
371 	if(version < 4) {
372 		QString create_vis_styles = QString("CREATE TABLE VisualStyles ") +
373 				"( " +
374 				"  name VARCHAR(255) PRIMARY KEY, " +
375 				"  col1 VARCHAR(20), "
376 				"  col2 VARCHAR(20), "
377 				"  col3 VARCHAR(20), "
378 				"  col4 VARCHAR(20), "
379 				"  nBinsSpectrum INTEGER, "
380 				"  rectHeightSpectrum INTEGER, "
381 				"  fadingStepsSpectrum INTEGER, "
382 				"  horSpacingSpectrum INTEGER, "
383 				"  vertSpacingSpectrum INTEGER, "
384 				"  rectWidthLevel INTEGER, "
385 				"  rectHeightLevel INTEGER, "
386 				"  horSpacingLevel INTEGER, "
387 				"  verSpacingLevel INTEGER, "
388 				"  fadingStepsLevel INTEGER "
389 				");";
390 
391 		bool success = checkAndCreateTable("VisualStyles", create_vis_styles);
392 		if(success) settingsConnector()->storeSetting("version", 4);
393 	}
394 
395 	if(version < 5) {
396 		bool success = checkAndInsertColumn("tracks", "rating", "integer");
397 		if(success) settingsConnector()->storeSetting("version", 5);
398 	}
399 
400 	if(version < 6) {
401 		QString create_savedbookmarks = QString("CREATE TABLE savedbookmarks ") +
402 				"( " +
403 				"	trackid INTEGER, " +
404 				"	name VARCHAR(255), " +
405 				"	timeidx INTEGER, " +
406 				"   PRIMARY KEY (trackid, timeidx), " +
407 				"   FOREIGN KEY (trackid) REFERENCES tracks(trackid) " +
408 				");";
409 
410 		bool success = checkAndCreateTable("savedbookmarks", create_savedbookmarks);
411 		if(success) settingsConnector()->storeSetting("version", 6);
412 	}
413 
414 	if(version < 7) {
415 		bool success = checkAndInsertColumn("albums", "rating", "integer");
416 		if(success) settingsConnector()->storeSetting("version", 7);
417 	}
418 
419 	if(version < 9) {
420 		bool success = checkAndInsertColumn("playlists", "temporary", "integer");
421 
422 		if(success) {
423 			Query q(this);
424 			QString querytext = "UPDATE playlists SET temporary=0;";
425 			q.prepare(querytext);
426 			if(q.exec()){
427 				settingsConnector()->storeSetting("version", 9);
428 			};
429 		}
430 	}
431 
432 	if(version < 10){
433 		bool success = checkAndInsertColumn("playlisttotracks", "db_id", "integer");
434 		if(success) {
435 			Query q(this);
436 			Query q_index(this);
437 			QString querytext = "UPDATE playlisttotracks SET db_id = (CASE WHEN trackid > 0 THEN 0 ELSE -1 END)";
438 			QString index_query = "CREATE INDEX album_search ON albums(cissearch, albumID);"
439 					"CREATE INDEX artist_search ON artists(cissearch, artistID);"
440 					"CREATE INDEX track_search ON tracks(cissearch, trackID);";
441 
442 			q.prepare(querytext);
443 			q_index.prepare(index_query);
444 
445 			if(q.exec()){
446 				settingsConnector()->storeSetting("version", 10);
447 			};
448 
449 			q_index.exec();
450 		}
451 	}
452 
453 	if(version < 11)
454 	{
455 		// look in UpdateDatesThread
456 	}
457 
458 	if(version < 12){
459 		QString querytext =
460 				"CREATE VIEW album_info_view AS "
461 				"SELECT "
462 				"	albums.albumID as albumID, "
463 				"	albums.name as name, "
464 				"	albums.cissearch as cissearch, "
465 				"	albums.rating as rating, "
466 				"	COUNT(artists.artistID) as artistCount, "
467 				"	COUNT(tracks.trackID) as trackCount, "
468 				"	CASE WHEN COUNT(DISTINCT artists.artistID) > 1 "
469 				"	THEN 1 "
470 				"	ELSE 0 "
471 				"	END as Sampler "
472 				"FROM albums, artists, tracks "
473 				"WHERE albums.albumID = tracks.albumID "
474 				"AND artists.artistID = tracks.artistID "
475 				"GROUP BY albums.albumID, albums.name";
476 			;
477 
478 		Query q(this);
479 		q.prepare(querytext);
480 
481 		if(q.exec()){
482 			settingsConnector()->storeSetting("version", 12);
483 		}
484 	}
485 
486 	if(version < 13){
487 		bool success = checkAndInsertColumn("tracks", "albumArtistID", "integer", "-1");
488 
489 		Query q(this);
490 		q.prepare("UPDATE tracks SET albumArtistID=artistID;");
491 		success = success && q.exec();
492 
493 		if(success){
494 			settingsConnector()->storeSetting("version", 13);
495 		}
496 	}
497 
498 	if(version < 14){
499 		bool success=checkAndInsertColumn("tracks", "libraryID", "integer", "0");
500 		Query q(this);
501 		q.prepare("UPDATE tracks SET libraryID=0;");
502 		success = success && q.exec();
503 
504 		if(success){
505 			settingsConnector()->storeSetting("version", 14);
506 		}
507 	}
508 
509 	if(version < 15)
510 	{
511 		QString create_string =
512 			"CREATE TABLE Libraries "
513 			"( "
514 			"  libraryID INTEGER NOT NULL, "
515 			"  libraryName VARCHAR(128) NOT NULL, "
516 			"  libraryPath VARCHAR(512) NOT NULL, "
517 			"  libraryIndex INTEGER NOT NULL,"
518 			"  PRIMARY KEY (libraryID, libraryPath) "
519 			"); ";
520 
521 		bool success=checkAndCreateTable("Libraries", create_string);
522 		if(success)
523 		{
524 			settingsConnector()->storeSetting("version", 15);
525 		}
526 	}
527 
528 	if(version < 16)
529 	{
530 		bool success = checkAndInsertColumn("tracks", "fileCissearch", "VARCHAR(256)");
531 
532 		if(success)
533 		{
534 			settingsConnector()->storeSetting("version", 16);
535 
536 			MetaDataList v_md;
537 			LibraryDatabase* lib_db = new DB::LibraryDatabase(connectionName(), databaseId(), -1);
538 			lib_db->getAllTracks(v_md);
539 			this->transaction();
540 			for(const MetaData& md : v_md) {
541 				lib_db->updateTrack(md);
542 			}
543 			this->commit();
544 
545 			delete lib_db;
546 		}
547 	}
548 
549 	if(version < 17)
550 	{
551 		bool success = checkAndInsertColumn("tracks", "comment", "VARCHAR(1024)");
552 
553 		if(success)
554 		{
555 			settingsConnector()->storeSetting("version", 17);
556 		}
557 	}
558 
559 	if(version < 18)
560 	{
561 		if(updateLostArtists() && updateLostAlbums())
562 		{
563 			settingsConnector()->storeSetting("version", 18);
564 		}
565 	}
566 
567 	if(version < 19)
568 	{
569 		QString create_string =
570 			"CREATE TABLE Shortcuts "
571 			"( "
572 			"  id INTEGER NOT NULL PRIMARY KEY, "
573 			"  identifier VARCHAR(32) NOT NULL, "
574 			"  shortcut VARCHAR(32) NOT NULL "
575 			"); ";
576 
577 		bool success = checkAndCreateTable("Shortcuts", create_string);
578 		if(success)
579 		{
580 			QString raw;
581 			settingsConnector()->loadSetting("shortcuts", raw);
582 
583 			RawShortcutMap rsm = RawShortcutMap::fromString(raw);
584 			for(const QString& key : rsm.keys())
585 			{
586 				this->shortcutConnector()->setShortcuts(key, rsm.value(key));
587 			}
588 
589 			settingsConnector()->storeSetting("shortcuts", "<deprecated>");
590 			settingsConnector()->storeSetting("version", 19);
591 		}
592 	}
593 
594 	if(version < 20)
595 	{
596 		checkAndDropTable("Covers");
597 
598 		bool success;
599 		{
600 			QString create_string =
601 				"CREATE TABLE Covers "
602 				"("
603 				"  coverId INTEGER PRIMARY KEY,"
604 				"  hash VARCHAR(64),"
605 				"  coverKey VARCHAR(128),"
606 				"  data BLOB "
607 				");";
608 
609 			success = checkAndCreateTable("Covers", create_string);
610 		}
611 
612 		{
613 			QString create_string =
614 				"CREATE TABLE TrackCoverMap "
615 				"("
616 				"  metadataId INTEGER,"
617 				"  coverId INTEGER,"
618 				"  PRIMARY KEY(metadataId, coverId),"
619 				"  FOREIGN KEY(metadataId) REFERENCES Tracks(trackId) ON DELETE CASCADE,"
620 				"  FOREIGN KEY(coverId) REFERENCES Covers(coverId) ON DELETE CASCADE"
621 				");";
622 
623 			success &= checkAndCreateTable("TrackCoverMap", create_string);
624 		}
625 
626 		{
627 			QString create_string =
628 				"CREATE TABLE AlbumCoverMap "
629 				"("
630 				"  albumId INTEGER,"
631 				"  coverId INTEGER,"
632 				"  PRIMARY KEY(albumId, coverId),"
633 				"  FOREIGN KEY(albumId) REFERENCES Albums(albumId) ON DELETE CASCADE,"
634 				"  FOREIGN KEY(coverId) REFERENCES Covers(coverId) ON DELETE CASCADE"
635 				");";
636 
637 			success &= checkAndCreateTable("AlbumCoverMap", create_string);
638 		}
639 
640 		if(success)
641 		{
642 			settingsConnector()->storeSetting("version", 20);
643 		}
644 	}
645 
646 	if(version < 21)
647 	{
648 		checkAndDropTable("Statistics");
649 		checkAndDropTable("Lyrics");
650 		checkAndDropTable("Genres");
651 
652 		settingsConnector()->storeSetting("version", 21);
653 	}
654 
655 	if(version < 22)
656 	{
657 		LibraryDatabase* lib_db = libraryDatabase(-1, 0);
658 
659 		QMap<QString, AlbumId> albums;
660 		QMap<QString, ArtistId> artists;
661 
662 		MetaDataList tracks;
663 		lib_db->getAllTracks(tracks);
664 
665 		for(auto it=tracks.begin(); it != tracks.end(); it++)
666 		{
667 			albums[it->album()] = it->albumId();
668 			artists[it->artist()] = it->artistId();
669 			artists[it->albumArtist()] = it->albumArtistId();
670 		}
671 
672 		for(auto it=tracks.begin(); it != tracks.end(); it++)
673 		{
674 			AlbumId correct_albumId = albums[it->album()];
675 			ArtistId correct_artistId = artists[it->artist()];
676 			ArtistId correct_album_artistId = artists[it->albumArtist()];
677 			this->transaction();
678 			if(	(it->albumId() != correct_albumId) ||
679 				(it->artistId() != correct_artistId) ||
680 				(it->albumArtistId() != correct_album_artistId))
681 			{
682 				spLog(Log::Info, this) << "Move track " << it->filepath() << "from album " << it->albumId() << " to " << correct_albumId;
683 
684 				it->setAlbumId(correct_albumId);
685 				it->setArtistId(correct_artistId);
686 				it->setAlbumArtistId(correct_album_artistId);
687 
688 				lib_db->updateTrack(*it);
689 			}
690 			this->commit();
691 		}
692 
693 		{
694 			QString query("DELETE FROM albums WHERE albums.albumID NOT IN (SELECT albumId from tracks);");
695 			Query q(this);
696 			q.prepare(query);
697 			q.exec();
698 		}
699 
700 		{
701 			QString query("DELETE FROM artists WHERE artists.artistID NOT IN (SELECT artistId from tracks UNION SELECT albumArtistId FROM tracks);");
702 			Query q(this);
703 			q.prepare(query);
704 			q.exec();
705 		}
706 
707 		settingsConnector()->storeSetting("version", 22);
708 	}
709 
710 	if(version < 23)
711 	{
712 		{
713 			checkAndCreateTable(
714 				"Sessions",
715 				"CREATE TABLE IF NOT EXISTS Sessions "
716 				"("
717 				"    sessionID INTEGER, "
718 				"    date INTEGER, "
719 				"    trackID INTEGER DEFAULT -1 REFERENCES Tracks(trackID) ON DELETE SET DEFAULT, "
720 				"    title VARCHAR(128), "
721 				"    artist VARCHAR(128), "
722 				"    album VARCHAR(128)"
723 				");"
724 			);
725 		}
726 
727 		{
728 			const QString query
729 			(
730 				"INSERT INTO Sessions "
731 				"SELECT session.date, session.date, session.trackID, tracks.title, artists.name, albums.name "
732 				"FROM Session session, Tracks tracks, Artists artists, Albums albums "
733 				"WHERE tracks.artistID = artists.artistID AND tracks.albumID = albums.albumID AND Session.trackID = tracks.trackID "
734 				";"
735 			);
736 
737 			Query q(this);
738 			q.prepare(query);
739 			q.exec();
740 		}
741 
742 		{
743 			checkAndDropTable("Session");
744 		}
745 
746 		settingsConnector()->storeSetting("version", 23);
747 	}
748 
749 	if(version < 24)
750 	{
751 
752 		{
753 			const QString query
754 			(
755 				"UPDATE Sessions SET sessionID = (sessionID + 20000000000000) WHERE sessionID < 1000000000000;"
756 			);
757 
758 			Query q(this);
759 			q.prepare(query);
760 			q.exec();
761 
762 		}
763 		{
764 			const QString query
765 			(
766 				"UPDATE Sessions SET date = (date + 20000000000000) WHERE date < 1000000000000;"
767 			);
768 
769 			Query q(this);
770 			q.prepare(query);
771 			q.exec();
772 		}
773 
774 		settingsConnector()->storeSetting("version", 24);
775 	}
776 
777 	if(version < 25)
778 	{
779 		checkAndInsertColumn("savedpodcasts", "reversed", "INTEGER", "0");
780 		settingsConnector()->storeSetting("version", 25);
781 	}
782 
783 	if(version < 26)
784 	{
785 		checkAndInsertColumn("playlistToTracks", "stationName", "VARCHAR(255)");
786 		checkAndInsertColumn("playlistToTracks", "station", "VARCHAR(512)");
787 		checkAndInsertColumn("playlistToTracks", "isRadio", "INTEGER", "0");
788 
789 		settingsConnector()->storeSetting("version", 26);
790 	}
791 
792 	if(version < 27)
793 	{
794 		bool success = checkAndInsertColumn("tracks", "genreCissearch", "VARCHAR(512)");
795 
796 		if(success)
797 		{
798 			settingsConnector()->storeSetting("version", 27);
799 
800 			MetaDataList tracks;
801 			LibraryDatabase* libraryDb = new DB::LibraryDatabase(connectionName(), databaseId(), -1);
802 			libraryDb->getAllTracks(tracks);
803 			this->transaction();
804 			for(const MetaData& md : tracks) {
805 				libraryDb->updateTrack(md);
806 			}
807 			this->commit();
808 
809 			delete libraryDb;
810 		}
811 	}
812 
813 	if(version < 28)
814 	{
815 		bool success = checkAndInsertColumn("playlistToTracks", "coverDownloadUrl", "VARCHAR(512)");
816 		if(success)
817 		{
818 			settingsConnector()->storeSetting("version", 28);
819 		}
820 	}
821 
822 	if(version < 29)
823 	{
824 		const auto createStatement = R"create(
825 			CREATE TABLE Equalizer
826             (
827               id INTEGER PRIMARY KEY AUTOINCREMENT,
828               name VARCHAR(32),
829               equalizerValues VARCHAR(32),
830               defaultValues VARCHAR(32)
831             );
832 			)create";
833 
834 		bool success = checkAndCreateTable("Equalizer", createStatement);
835 		success &= equalizerConnector()->restoreFactoryDefaults();
836 
837 		if(success) {
838 			settingsConnector()->storeSetting("version", 29);
839 		}
840 	}
841 
842 	return true;
843 }
844 
libraryDatabases() const845 DB::LibraryDatabases Connector::libraryDatabases() const
846 {
847 	return m->libraryDbs;
848 }
849 
libraryDatabase(LibraryId libraryId,DbId databaseId)850 DB::LibraryDatabase* Connector::libraryDatabase(LibraryId libraryId, DbId databaseId)
851 {
852 	LibDbIterator it = Algorithm::find(m->libraryDbs, [&](DB::LibraryDatabase* db){
853 		return (db->libraryId() == libraryId && db->databaseId() == databaseId);
854 	});
855 
856 	if(it == m->libraryDbs.end())
857 	{
858 		spLog(Log::Warning, this) << "Could not find Library:"
859 								" DB ID = " << int(databaseId)
860 							 << " LibraryID = " << int(libraryId);
861 
862 		return m->genericLibraryDatabase;
863 	}
864 
865 	return *it;
866 }
867 
868 
registerLibraryDatabase(LibraryId libraryId)869 DB::LibraryDatabase* Connector::registerLibraryDatabase(LibraryId libraryId)
870 {
871 	DB::LibraryDatabase* lib_db = nullptr;
872 	LibDbIterator it = Algorithm::find(m->libraryDbs, [=](DB::LibraryDatabase* db){
873 		return (db->libraryId() == libraryId);
874 	});
875 
876 	if(it == m->libraryDbs.end())
877 	{
878 		lib_db = new DB::LibraryDatabase(this->connectionName(), this->databaseId(), libraryId);
879 		m->libraryDbs << lib_db;
880 	}
881 
882 	else
883 	{
884 		lib_db = *it;
885 	}
886 
887 	return lib_db;
888 }
889 
deleteLibraryDatabase(LibraryId libraryId)890 void Connector::deleteLibraryDatabase(LibraryId libraryId)
891 {
892 	LibDbIterator it = Algorithm::find(m->libraryDbs, [=](DB::LibraryDatabase* db){
893 		return (db->libraryId() == libraryId);
894 	});
895 
896 	if(it != m->libraryDbs.end())
897 	{
898 		LibraryDatabase* db = *it;
899 		db->deleteAllTracks(true);
900 		m->libraryDbs.removeAll(db);
901 
902 		delete db; db = nullptr;
903 	}
904 }
905 
906 
playlistConnector()907 DB::Playlist* Connector::playlistConnector()
908 {
909 	if(!m->playlistConnector){
910 		m->playlistConnector = new DB::Playlist(this->connectionName(), this->databaseId());
911 	}
912 
913 	return m->playlistConnector;
914 }
915 
916 
bookmarkConnector()917 DB::Bookmarks* Connector::bookmarkConnector()
918 {
919 	if(!m->bookmarkConnector){
920 		m->bookmarkConnector = new DB::Bookmarks(this->connectionName(), this->databaseId());
921 	}
922 
923 	return m->bookmarkConnector;
924 }
925 
streamConnector()926 DB::Streams* Connector::streamConnector()
927 {
928 	if(!m->streamConnector){
929 		m->streamConnector = new DB::Streams(this->connectionName(), this->databaseId());
930 	}
931 
932 	return m->streamConnector;
933 }
934 
podcastConnector()935 DB::Podcasts* Connector::podcastConnector()
936 {
937 	if(!m->podcastConnector){
938 		m->podcastConnector = new DB::Podcasts(this->connectionName(), this->databaseId());
939 	}
940 
941 	return m->podcastConnector;
942 }
943 
visualStyleConnector()944 DB::VisualStyles* Connector::visualStyleConnector()
945 {
946 	if(!m->visualStyleConnector){
947 		m->visualStyleConnector = new DB::VisualStyles(this->connectionName(), this->databaseId());
948 	}
949 
950 	return m->visualStyleConnector;
951 }
952 
settingsConnector()953 DB::Settings* Connector::settingsConnector()
954 {
955 	if(!m->settingsConnector){
956 		m->settingsConnector = new DB::Settings(this->connectionName(), this->databaseId());
957 	}
958 
959 	return m->settingsConnector;
960 }
961 
shortcutConnector()962 DB::Shortcuts*Connector::shortcutConnector()
963 {
964 	if(!m->shortcutConnector){
965 		m->shortcutConnector = new DB::Shortcuts(this->connectionName(), this->databaseId());
966 	}
967 
968 	return m->shortcutConnector;
969 }
970 
libraryConnector()971 DB::Library* Connector::libraryConnector()
972 {
973 	if(!m->libraryConnector){
974 		m->libraryConnector = new DB::Library(this->connectionName(), this->databaseId());
975 	}
976 
977 	return m->libraryConnector;
978 }
979 
coverConnector()980 DB::Covers* Connector::coverConnector()
981 {
982 	if(!m->coverConnector){
983 		m->coverConnector = new DB::Covers(this->connectionName(), this->databaseId());
984 	}
985 
986 	return m->coverConnector;
987 }
988 
sessionConnector()989 DB::Session* DB::Connector::sessionConnector()
990 {
991 	if(!m->sessionConnector){
992 		m->sessionConnector = new DB::Session(this->connectionName(), this->databaseId());
993 	}
994 
995 	return m->sessionConnector;
996 }
997 
equalizerConnector()998 DB::Equalizer* DB::Connector::equalizerConnector()
999 {
1000 	if(!m->equalizerConnector){
1001 		m->equalizerConnector = new DB::Equalizer(this->connectionName(), this->databaseId());
1002 	}
1003 
1004 	return m->equalizerConnector;
1005 }
1006 
what() const1007 const char* DatabaseNotCreatedException::what() const noexcept
1008 {
1009 	return "Database could not be created";
1010 }
1011