1 /* LibraryDatabase.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 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  * 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 Public License for more details.
16 
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "LibraryDatabase.h"
22 #include "Database/Query.h"
23 #include "Database/CoverConnector.h"
24 #include "Utils/Settings/Settings.h"
25 #include "Utils/MetaData/MetaDataList.h"
26 #include "Utils/MetaData/Album.h"
27 #include "Utils/MetaData/Artist.h"
28 #include "Utils/Set.h"
29 #include "Utils/Logger/Logger.h"
30 #include "Utils/Library/SearchMode.h"
31 
32 using DB::LibraryDatabase;
33 using DB::Query;
34 
35 using SMM=::Library::SearchModeMask;
36 
37 struct LibraryDatabase::Private
38 {
39 	QString artistIdField;
40 	QString artistNameField;
41 	QString connectionName;
42 
43 	SMM		searchMode;
44 	DbId	databaseId;
45 
46 	LibraryId libraryId;
47 
PrivateLibraryDatabase::Private48 	Private(const QString& connectionName, DbId databaseId, LibraryId libraryId) :
49 		connectionName(connectionName),
50 		databaseId(databaseId),
51 		libraryId(libraryId)
52 	{
53 		searchMode = GetSetting(Set::Lib_SearchMode);
54 
55 		artistIdField = "artistID";
56 		artistNameField = "artistName";
57 	}
58 };
59 
60 
LibraryDatabase(const QString & connectionName,DbId databaseId,LibraryId libraryId)61 LibraryDatabase::LibraryDatabase(const QString& connectionName, DbId databaseId, LibraryId libraryId) :
62 	DB::Albums(),
63 	DB::Artists(),
64 	DB::Tracks(),
65 	DB::Module(connectionName, databaseId)
66 {
67 	m = Pimpl::make<Private>(connectionName, databaseId, libraryId);
68 
69 	DB::Tracks::initViews();
70 	DB::Albums::initViews();
71 
72 	{ // set artistId field
73 		AbstrSetting* s = Settings::instance()->setting(SettingKey::Lib_ShowAlbumArtists);
74 		QString dbKey = s->dbKey();
75 
76 		Query q(connectionName, databaseId);
77 		QString querytext = "SELECT value FROM settings WHERE key = '" + dbKey + "';";
78 
79 		bool show_album_artists = false;
80 
81 		q.prepare(querytext);
82 		if(q.exec())
83 		{
84 			if(q.next())
85 			{
86 				QVariant var = q.value("value");
87 				show_album_artists = var.toBool();
88 			}
89 		}
90 
91 		if(show_album_artists) {
92 			changeArtistIdField(LibraryDatabase::ArtistIDField::AlbumArtistID);
93 		}
94 
95 		else {
96 			changeArtistIdField(LibraryDatabase::ArtistIDField::ArtistID);
97 		}
98 	}
99 }
100 
101 LibraryDatabase::~LibraryDatabase() = default;
102 
changeArtistIdField(LibraryDatabase::ArtistIDField field)103 void LibraryDatabase::changeArtistIdField(LibraryDatabase::ArtistIDField field)
104 {
105 	if(field == LibraryDatabase::ArtistIDField::AlbumArtistID)
106 	{
107 		m->artistIdField = "albumArtistID";
108 		m->artistNameField = "albumArtistName";
109 	}
110 
111 	else
112 	{
113 		m->artistIdField = "artistID";
114 		m->artistNameField = "artistName";
115 	}
116 }
117 
artistIdField() const118 QString LibraryDatabase::artistIdField() const
119 {
120 	return m->artistIdField;
121 }
122 
artistNameField() const123 QString LibraryDatabase::artistNameField() const
124 {
125 	return m->artistNameField;
126 }
127 
trackView() const128 QString LibraryDatabase::trackView() const
129 {
130 	if(m->libraryId < 0) {
131 		return "tracks";
132 	}
133 
134 	else {
135 		return QString("track_view_%1").arg(m->libraryId);
136 	}
137 }
138 
trackSearchView() const139 QString LibraryDatabase::trackSearchView() const
140 {
141 	if(m->libraryId < 0) {
142 		return "track_search_view";
143 	}
144 
145 	else {
146 		return QString("track_search_view_%1").arg(m->libraryId);
147 	}
148 }
149 
updateSearchMode()150 void LibraryDatabase::updateSearchMode()
151 {
152 	auto currentSearchModeMask = GetSetting(Set::Lib_SearchMode);
153 
154 	if(m->searchMode != currentSearchModeMask)
155 	{
156 		DB::Albums::updateAlbumCissearch();
157 		DB::Artists::updateArtistCissearch();
158 		DB::Tracks::updateTrackCissearch();
159 	}
160 
161 	m->searchMode = currentSearchModeMask;
162 }
163 
164 using AlbumHash=QString;
calcAlbumHash(const QString & albumName,const QString & albumArtist)165 static AlbumHash calcAlbumHash(const QString& albumName, const QString& albumArtist)
166 {
167 	return albumName.toLower() + albumArtist.toLower();
168 }
calcAlbumHash(const Album & album)169 static AlbumHash calcAlbumHash(const Album& album)
170 {
171 	return calcAlbumHash(album.name(), album.albumArtist());
172 }
173 
insertMissingArtistsAndAlbums(const MetaDataList & tracks)174 MetaDataList LibraryDatabase::insertMissingArtistsAndAlbums(const MetaDataList& tracks)
175 {
176 	if(tracks.isEmpty()){
177 		return tracks;
178 	}
179 
180 	spLog(Log::Develop, this) << " Search for already known albums and artists.";
181 
182 	// gather all albums in a map
183 	QHash<AlbumHash, Album> albumMap;
184 	{
185 		AlbumList albums;
186 		DB::Albums::getAllAlbums(albums, true);
187 
188 		for(const Album& album : albums)
189 		{
190 			AlbumHash hash = calcAlbumHash(album);
191 			albumMap[hash] = album;
192 		}
193 	}
194 
195 	// gather all artists in a map
196 	QHash<QString, Artist> artistMap;
197 	{
198 		ArtistList artists;
199 		DB::Artists::getAllArtists(artists, true);
200 
201 		for(const Artist& artist : artists){
202 			artistMap[artist.name()] = artist;
203 		}
204 	}
205 
206 	// gather all metadata in a map
207 	QHash<QString, MetaData> trackMap;
208 	{
209 		MetaDataList knownTracks;
210 		DB::Tracks::getAllTracks(knownTracks);
211 		for(const MetaData& md : knownTracks) {
212 			trackMap[md.filepath()] = md;
213 		}
214 	}
215 
216 	db().transaction();
217 
218 	MetaDataList ret(tracks);
219 	for(MetaData& md : ret)
220 	{
221 		if(md.libraryId() < 0) {
222 			md.setLibraryid(m->libraryId);
223 		}
224 
225 		{ // check album id
226 			AlbumHash hash = calcAlbumHash(md.album(), {md.albumArtist()});
227 			Album album = albumMap[hash];
228 			if(album.id() < 0)
229 			{
230 				AlbumId id = DB::Albums::insertAlbumIntoDatabase(md.album());
231 				spLog(Log::Debug, this) << "Insert new album " << hash << " (" << md.album() << "): " << id;
232 				album.setId(id);
233 				albumMap[hash] = album;
234 			}
235 
236 			md.setAlbumId(album.id());
237 		}
238 
239 		{ // check artist id
240 			Artist artist = artistMap[md.artist()];
241 			if(artist.id() < 0)
242 			{
243 				ArtistId id = DB::Artists::insertArtistIntoDatabase(md.artist());
244 				spLog(Log::Debug, this) << "Insert new artist " << md.artist() << ": " << id;
245 				artist.setId(id);
246 				artistMap[md.artist()] = artist;
247 			}
248 
249 			md.setArtistId(artist.id());
250 		}
251 
252 		{ // check album artist ...
253 			Artist albumArtist = artistMap[md.albumArtist()];
254 			if(albumArtist.id() < 0)
255 			{
256 				ArtistId id = DB::Artists::insertArtistIntoDatabase(md.albumArtist());
257 				spLog(Log::Debug, this) << "Insert new albumArtist " << md.albumArtist() << ": " << id;
258 				albumArtist.setId(id);
259 				artistMap[md.albumArtist()] = albumArtist;
260 			}
261 
262 			md.setAlbumArtistId(albumArtist.id());
263 		}
264 
265 		{ // check track id
266 			TrackID id = trackMap[md.filepath()].id();
267 			md.setId(id);
268 		}
269 	}
270 
271 	db().commit();
272 
273 	return ret;
274 }
275 
fixEmptyAlbums()276 bool LibraryDatabase::fixEmptyAlbums()
277 {
278 	AlbumId id = DB::Albums::insertAlbumIntoDatabase(QString(""));
279 
280 	const QStringList queries {
281 		QString("UPDATE tracks SET albumID=:albumID WHERE albumID IN (SELECT albumID FROM albums WHERE name IS NULL);"),
282 		QString("UPDATE tracks SET albumID=:albumID WHERE albumID NOT IN (SELECT albumID FROM albums);"),
283 		QString("DELETE FROM artists WHERE name IS NULL;")
284 	};
285 
286 	db().transaction();
287 	for(const QString& query : queries)
288 	{
289 		DB::Query q(this);
290 		q.prepare(query);
291 		q.bindValue(":albumID", id);
292 		bool success = q.exec();
293 		if(!success){
294 			db().rollback();
295 			return false;
296 		}
297 	}
298 
299 	return db().commit();
300 }
301 
module()302 DB::Module* LibraryDatabase::module()
303 {
304 	return this;
305 }
306 
module() const307 const DB::Module* LibraryDatabase::module() const
308 {
309 	return this;
310 }
311 
clear()312 void LibraryDatabase::clear()
313 {
314 	DB::Tracks::deleteAllTracks(false);
315 	DB::Albums::deleteAllAlbums();
316 	DB::Artists::deleteAllArtists();
317 }
318 
libraryId() const319 LibraryId LibraryDatabase::libraryId() const
320 {
321 	return m->libraryId;
322 }
323 
storeMetadata(const MetaDataList & tracks)324 bool DB::LibraryDatabase::storeMetadata(const MetaDataList& tracks)
325 {
326 	if(tracks.isEmpty()) {
327 		return true;
328 	}
329 
330 	MetaDataList modifiedTracks = insertMissingArtistsAndAlbums(tracks);
331 
332 	db().transaction();
333 
334 	for(const MetaData& md : modifiedTracks)
335 	{
336 		// because all artists and albums should be in the db right now,
337 		// we should never reach the inner block
338 		if(md.albumId() < 0 || md.artistId() < 0 || md.libraryId() < 0)
339 		{
340 			spLog(Log::Warning, this) << "Cannot insert artist or album of " << md.filepath();
341 			continue;
342 		}
343 
344 		// check, if the track was known before
345 		{
346 			if(md.id() < 0) {
347 				DB::Tracks::insertTrackIntoDatabase(md, md.artistId(), md.albumId(), md.albumArtistId());
348 			}
349 
350 			else {
351 				DB::Tracks::updateTrack(md);
352 			}
353 		}
354 	}
355 
356 	spLog(Log::Develop, this) << "Commit " << tracks.size() << " tracks to database";
357 
358 	return db().commit();
359 }
360