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