1 /* DatabaseTracks.cpp */
2
3 /* Copyright (C) 2011-2020 Michael Lugmair (Lucio Carreras)
4 *
5 * module() file is part of sayonara player
6 *
7 * module() 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 * module() 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 module() program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "Database/Tracks.h"
22 #include "Database/Library.h"
23 #include "Database/Query.h"
24
25 #include "Utils/MetaData/MetaDataList.h"
26 #include "Utils/MetaData/Genre.h"
27
28 #include "Utils/Algorithm.h"
29 #include "Utils/Utils.h"
30 #include "Utils/FileUtils.h"
31 #include "Utils/Set.h"
32 #include "Utils/Library/Filter.h"
33 #include "Utils/Library/SearchMode.h"
34 #include "Utils/Logger/Logger.h"
35
36 #include <QDateTime>
37 #include <QMap>
38
39 #include <utility>
40 #include <tuple>
41
42 using DB::Tracks;
43 using DB::Query;
44 using SMM=::Library::SearchModeMask;
45 namespace LibraryUtils=::Library::Utils;
46 using ::Library::Filter;
47
48 static QString getFilterClause(const Filter& filter, QString cisPlaceholder);
49
dropTrackView(DB::Module * module,LibraryId libraryId,const QString & trackView)50 static void dropTrackView(DB::Module* module, LibraryId libraryId, const QString& trackView)
51 {
52 if(libraryId < 0){
53 return;
54 }
55
56 module->runQuery("DROP VIEW IF EXISTS " + trackView + ";", "Cannot drop " + trackView);
57 }
58
dropSearchView(DB::Module * module,const QString & trackSearchView)59 static void dropSearchView(DB::Module* module, const QString& trackSearchView)
60 {
61 module->runQuery("DROP VIEW IF EXISTS " + trackSearchView + "; ", "Cannot drop " + trackSearchView);
62 }
63
createTrackView(DB::Module * module,LibraryId libraryId,const QString & trackView,const QString & selectStatement)64 static void createTrackView(DB::Module* module, LibraryId libraryId, const QString& trackView, const QString& selectStatement)
65 {
66 if(libraryId < 0){
67 return;
68 }
69
70 QString query = "CREATE VIEW "
71 + trackView + " "
72 "AS " + selectStatement + " "
73 "FROM tracks "
74 "WHERE tracks.libraryID = " + QString::number(libraryId);
75
76 module->runQuery(query, "Cannot create track view");
77 }
78
createTrackSearchView(DB::Module * module,LibraryId libraryId,const QString & trackSearchView,const QString & selectStatement)79 static void createTrackSearchView(DB::Module* module, LibraryId libraryId, const QString& trackSearchView, const QString& selectStatement)
80 {
81 QString query =
82 "CREATE VIEW "
83 + trackSearchView + " "
84 "AS "
85 + selectStatement + ", "
86 "albums.name AS albumName, " // 18
87 "albums.rating AS albumRating, " // 19
88 "artists.name AS artistName, " // 20
89 "albumArtists.name AS albumArtistName, " // 21
90 "(albums.cissearch || ',' || artists.cissearch || ',' || tracks.cissearch) AS allCissearch, " // 22
91 "tracks.fileCissearch AS fileCissearch, " // 23
92 "tracks.genreCissearch AS genreCissearch " // 24
93 "FROM tracks "
94 "LEFT OUTER JOIN albums ON tracks.albumID = albums.albumID "
95 "LEFT OUTER JOIN artists ON tracks.artistID = artists.artistID "
96 "LEFT OUTER JOIN artists albumArtists ON tracks.albumArtistID = albumArtists.artistID "
97 ;
98
99 if(libraryId >= 0) {
100 query += "WHERE libraryID=" + QString::number(libraryId);
101 }
102
103 query += ";";
104
105 module->runQuery(query, "Cannot create track search view");
106 }
107
108 Tracks::Tracks() = default;
109 Tracks::~Tracks() = default;
110
initViews()111 void Tracks::initViews()
112 {
113 const QStringList fields
114 {
115 "tracks.trackID", // 0
116 "tracks.title", // 1
117 "tracks.length", // 2
118 "tracks.year", // 3
119 "tracks.bitrate", // 4
120 "tracks.filename", // 5
121 "tracks.filesize", // 6
122 "tracks.track AS trackNum", // 7
123 "tracks.genre", // 8
124 "tracks.discnumber", // 9
125 "tracks.rating", // 10
126 "tracks.albumID AS albumID", // 11
127 "tracks.artistID AS artistID", // 12
128 "tracks.albumArtistID AS albumArtistID", // 13
129 "tracks.comment AS comment", // 14
130 "tracks.createDate", // 15
131 "tracks.modifyDate", // 16
132 "tracks.libraryID AS trackLibraryID" // 17
133 };
134
135 QString select = "SELECT " + fields.join(", ") + " ";
136
137 dropTrackView(module(), libraryId(), trackView());
138 createTrackView(module(), libraryId(), trackView(), select);
139
140 dropSearchView(module(), trackSearchView());
141 createTrackSearchView(module(), libraryId(), trackSearchView(), select);
142 }
143
fetchQueryTracks() const144 QString Tracks::fetchQueryTracks() const
145 {
146 return "SELECT * FROM " + trackSearchView() + " ";
147 }
148
dbFetchTracks(Query & q,MetaDataList & result) const149 bool Tracks::dbFetchTracks(Query& q, MetaDataList& result) const
150 {
151 result.clear();
152
153 if (!q.exec()) {
154 q.showError("Cannot fetch tracks from database");
155 return false;
156 }
157
158 while(q.next())
159 {
160 MetaData data;
161
162 data.setId( q.value(0).toInt());
163 data.setTitle( q.value(1).toString());
164 data.setDurationMs( q.value(2).toInt());
165 data.setYear( q.value(3).value<Year>());
166 data.setBitrate( q.value(4).value<Bitrate>());
167 data.setFilepath( q.value(5).toString());
168 data.setFilesize( q.value(6).value<Filesize>());
169 data.setTrackNumber( q.value(7).value<TrackNum>());
170 data.setGenres( q.value(8).toString().split(","));
171 data.setDiscnumber( q.value(9).value<Disc>());
172 data.setRating( q.value(10).value<Rating>());
173 data.setAlbumId( q.value(11).toInt());
174 data.setArtistId( q.value(12).toInt());
175 data.setComment( q.value(14).toString());
176 data.setCreatedDate( q.value(15).value<uint64_t>());
177 data.setModifiedDate( q.value(16).value<uint64_t>());
178 data.setLibraryid( q.value(17).value<LibraryId>());
179 data.setAlbum( q.value(18).toString().trimmed());
180 data.setArtist( q.value(20).toString().trimmed());
181 data.setAlbumArtist( q.value(21).toString(), q.value(13).toInt());
182
183 data.setDatabaseId(module()->databaseId());
184
185 result.push_back(std::move(data));
186 }
187
188 return true;
189 }
190
getMultipleTracksByPath(const QStringList & paths,MetaDataList & tracks) const191 bool Tracks::getMultipleTracksByPath(const QStringList& paths, MetaDataList& tracks) const
192 {
193 for(const QString& path : paths) {
194 tracks << getTrackByPath(path);
195 }
196
197 return (tracks.count() == paths.size());
198 }
199
getTrackByPath(const QString & path) const200 MetaData Tracks::getTrackByPath(const QString& path) const
201 {
202 const QString cleanedPath = Util::File::cleanFilename(path);
203 const QString query = fetchQueryTracks() + "WHERE filename = :filename;";
204
205 DB::Query q(module());
206 q.prepare(query);
207 q.bindValue(":filename", Util::convertNotNull(cleanedPath));
208
209 MetaData md(cleanedPath);
210 md.setDatabaseId(module()->databaseId());
211
212 MetaDataList tracks;
213 if(!dbFetchTracks(q, tracks)) {
214 return md;
215 }
216
217 if(tracks.empty())
218 {
219 md.setExtern(true);
220 return md;
221 }
222
223 return tracks.first();
224 }
225
getTrackById(TrackID id) const226 MetaData Tracks::getTrackById(TrackID id) const
227 {
228 Query q(module());
229 QString query = fetchQueryTracks() +
230 " WHERE trackID = :trackId; ";
231
232 q.prepare(query);
233 q.bindValue(":trackId", id);
234
235 MetaDataList tracks;
236 if(!dbFetchTracks(q, tracks)) {
237 return MetaData();
238 }
239
240 if(tracks.isEmpty()) {
241 MetaData md;
242 md.setExtern(true);
243 return md;
244 }
245
246 return tracks.first();
247 }
248
getNumTracks() const249 int Tracks::getNumTracks() const
250 {
251 DB::Query q = module()->runQuery(
252 "SELECT COUNT(tracks.trackid) FROM tracks WHERE libraryID=:libraryID;",
253 {":libraryID", libraryId()},
254 "Cannot count tracks"
255 );
256
257 if(q.hasError() || !q.next()){
258 return -1;
259 }
260
261 int ret = q.value(0).toInt();
262 return ret;
263 }
264
getTracksByIds(const QList<TrackID> & ids,MetaDataList & tracks) const265 bool Tracks::getTracksByIds(const QList<TrackID>& ids, MetaDataList& tracks) const
266 {
267 QStringList queries;
268 for(const TrackID& id : ids)
269 {
270 queries << fetchQueryTracks() + QString(" WHERE trackID = :trackId%1").arg(id);
271 }
272
273 QString query = queries.join(" UNION ");
274 query += ";";
275
276 Query q(module());
277 q.prepare(query);
278
279 for(TrackID id : ids)
280 {
281 q.bindValue(QString(":trackId%1").arg(id), id);
282 }
283
284 return dbFetchTracks(q, tracks);
285 }
286
getAllTracks(MetaDataList & result) const287 bool Tracks::getAllTracks(MetaDataList& result) const
288 {
289 Query q(module());
290
291 QString query = fetchQueryTracks() + ";";
292
293 q.prepare(query);
294
295 return dbFetchTracks(q, result);
296 }
297
getAllTracksByAlbum(const IdList & albumsIds,MetaDataList & result) const298 bool DB::Tracks::getAllTracksByAlbum(const IdList& albumsIds, MetaDataList& result) const
299 {
300 return getAllTracksByAlbum(albumsIds, result, Filter(), -1);
301 }
302
getAllTracksByAlbum(const IdList & albumIds,MetaDataList & result,const Filter & filter,int discnumber) const303 bool Tracks::getAllTracksByAlbum(const IdList& albumIds, MetaDataList& result, const Filter& filter, int discnumber) const
304 {
305 if(albumIds.isEmpty()) {
306 return false;
307 }
308
309 const QStringList searchFilters = filter.searchModeFiltertext(true);
310 for(const QString& searchFilter : searchFilters)
311 {
312 QString query = fetchQueryTracks();
313 query += " WHERE ";
314 if( !filter.cleared() )
315 {
316 query += getFilterClause(filter, ":cissearch") + " AND ";
317 }
318
319 { // album id clauses
320 const QString aidf = trackSearchView() + ".albumID ";
321
322 QStringList orClauses;
323 Util::Algorithm::transform(albumIds, orClauses, [aidf](AlbumId albumId){
324 return QString("%1 = :albumId%2").arg(aidf).arg(albumId);
325 });
326
327 query += " (" + orClauses.join(" OR ") + ") ";
328 }
329
330 query += ";";
331
332 { // prepare & run
333 Query q(module());
334 q.prepare(query);
335
336 for(AlbumId albumId : albumIds) {
337 q.bindValue(QString(":albumId%1").arg(albumId), albumId);
338 }
339
340 q.bindValue(":cissearch", searchFilter);
341
342 MetaDataList tmpList;
343 dbFetchTracks(q, tmpList);
344
345 if(discnumber >= 0)
346 {
347 tmpList.removeTracks([discnumber](const MetaData& md)
348 {
349 return (md.discnumber() != discnumber);
350 });
351 }
352
353 result.appendUnique(tmpList);
354 }
355 }
356
357 return true;
358 }
359
360
getAllTracksByAlbumArtist(const IdList & artistIds,MetaDataList & result) const361 bool Tracks::getAllTracksByAlbumArtist(const IdList& artistIds, MetaDataList& result) const
362 {
363 return getAllTracksByAlbumArtist(artistIds, result, Filter());
364 }
365
getAllTracksByAlbumArtist(const IdList & artistIds,MetaDataList & result,const Filter & filter) const366 bool Tracks::getAllTracksByAlbumArtist(const IdList& artistIds, MetaDataList& result, const Filter& filter) const
367 {
368 if(artistIds.empty()){
369 return false;
370 }
371
372 const QStringList searchFilters = filter.searchModeFiltertext(true);
373 for(const QString& searchFilter : searchFilters)
374 {
375 QString query = fetchQueryTracks();
376 query += " WHERE ";
377
378 if( !filter.cleared() )
379 {
380 query += getFilterClause(filter, ":cissearch") + " AND ";
381 }
382
383 { // artist conditions
384 const QString aidf = trackSearchView() + "." + artistIdField();
385
386 QStringList orClauses;
387 Util::Algorithm::transform(artistIds, orClauses, [aidf](ArtistId artistId){
388 return QString("%1 = :artistId%2").arg(aidf).arg(artistId);
389 });
390
391 query += " (" + orClauses.join(" OR ") + ") ";
392 }
393
394 query += ";";
395
396 { // prepare & run
397 Query q(module());
398 q.prepare(query);
399
400 for(ArtistId artistId : artistIds) {
401 q.bindValue(QString(":artistId%1").arg(artistId), artistId);
402 }
403
404 q.bindValue(":cissearch", searchFilter);
405
406 MetaDataList tmpList;
407 dbFetchTracks(q, tmpList);
408 result.appendUnique(tmpList);
409 }
410 }
411
412 return true;
413 }
414
415
getAllTracksByArtist(const IdList & artistIds,MetaDataList & result) const416 bool Tracks::getAllTracksByArtist(const IdList& artistIds, MetaDataList& result) const
417 {
418 return getAllTracksByArtist(artistIds, result, Filter());
419 }
420
getAllTracksByArtist(const IdList & artistIds,MetaDataList & result,const Filter & filter) const421 bool Tracks::getAllTracksByArtist(const IdList& artistIds, MetaDataList& result, const Filter& filter) const
422 {
423 if(artistIds.empty()){
424 return false;
425 }
426
427 const QStringList searchFilters = filter.searchModeFiltertext(true);
428 for(const QString& searchFilter : searchFilters)
429 {
430 QString query = fetchQueryTracks();
431 query += " WHERE ";
432
433 if( !filter.cleared() )
434 {
435 query += getFilterClause(filter, ":cissearch") + " AND ";
436 }
437
438 { // artist conditions
439 const QString aidf = trackSearchView() + "." + artistIdField();
440
441 QStringList orClauses;
442 Util::Algorithm::transform(artistIds, orClauses, [aidf](ArtistId artistId){
443 return QString("%1 = :artistId%2").arg(aidf).arg(artistId);
444 });
445
446 query += " (" + orClauses.join(" OR ") + ") ";
447 }
448
449 query += ";";
450
451 { // prepare & run
452 Query q(module());
453 q.prepare(query);
454
455 for(ArtistId artistId : artistIds) {
456 q.bindValue(QString(":artistId%1").arg(artistId), artistId);
457 }
458
459 q.bindValue(":cissearch", searchFilter);
460
461 MetaDataList tmpList;
462 dbFetchTracks(q, tmpList);
463 result.appendUnique(tmpList);
464 }
465 }
466
467 return true;
468 }
469
getAllTracksBySearchString(const Filter & filter,MetaDataList & result) const470 bool Tracks::getAllTracksBySearchString(const Filter& filter, MetaDataList& result) const
471 {
472 const QStringList searchFilters = filter.searchModeFiltertext(true);
473 for(const QString& searchFilter : searchFilters)
474 {
475 QString query = fetchQueryTracks();
476 query += " WHERE " + getFilterClause(filter, ":cissearch");
477 query += ";";
478
479 Query q(module());
480 q.prepare(query);
481 q.bindValue(":cissearch", searchFilter);
482
483 {
484 MetaDataList tracks;
485 dbFetchTracks(q, tracks);
486
487 result.appendUnique(tracks);
488 }
489 }
490
491 return true;
492 }
493
getAllTracksByPaths(const QStringList & paths,MetaDataList & tracks) const494 bool Tracks::getAllTracksByPaths(const QStringList& paths, MetaDataList& tracks) const
495 {
496 QStringList queries;
497 for(int i=0; i<paths.size(); i++)
498 {
499 queries << fetchQueryTracks() + " WHERE filename LIKE :" + QString("path%1").arg(i);
500 }
501
502 QString query = queries.join(" UNION ") + ";";
503 Query q(module());
504 q.prepare(query);
505 for(int i=0; i<paths.size(); i++)
506 {
507 q.bindValue( QString(":path%1").arg(i), paths[i] + "%");
508 }
509
510 bool success = dbFetchTracks(q, tracks);
511 return success;
512 }
513
deleteTrack(TrackID id)514 bool Tracks::deleteTrack(TrackID id)
515 {
516 Query q = module()->runQuery("DELETE FROM tracks WHERE trackID = :trackID", {":trackID", id}, QString("Cannot delete track %1").arg(id));
517
518 return (!q.hasError());
519 }
520
deleteTracks(const IdList & ids)521 bool Tracks::deleteTracks(const IdList& ids)
522 {
523 module()->db().transaction();
524
525 int fileCount = Util::Algorithm::count(ids, [this](Id id)
526 {
527 return deleteTrack(id);
528 });
529
530 bool success = module()->db().commit();
531
532 return (success && (fileCount == ids.size()));
533 }
534
deleteTracks(const MetaDataList & tracks)535 bool Tracks::deleteTracks(const MetaDataList& tracks)
536 {
537 if(tracks.isEmpty()){
538 return true;
539 }
540
541 module()->db().transaction();
542
543 auto deletedTracks = Util::Algorithm::count(tracks, [this](const MetaData& md)
544 {
545 return deleteTrack(md.id());
546 });
547
548 module()->db().commit();
549
550 spLog(Log::Info, this) << "Deleted " << deletedTracks << " of " << tracks.size() << " tracks";
551
552 return (deletedTracks == tracks.count());
553 }
554
deleteInvalidTracks(const QString & libraryPath,MetaDataList & doubleMetadata)555 bool Tracks::deleteInvalidTracks(const QString& libraryPath, MetaDataList& doubleMetadata)
556 {
557 doubleMetadata.clear();
558
559 MetaDataList tracks;
560 if(!getAllTracks(tracks))
561 {
562 spLog(Log::Error, this) << "Cannot get tracks from db";
563 return false;
564 }
565
566 QMap<QString, int> map;
567 IdList toDelete;
568 int idx = 0;
569
570 for(const MetaData& md : tracks)
571 {
572 if(map.contains(md.filepath()))
573 {
574 spLog(Log::Warning, this) << "found double path: " << md.filepath();
575 int oldIndex = map[md.filepath()];
576 toDelete << md.id();
577 doubleMetadata << tracks[oldIndex];
578 }
579
580 else {
581 map.insert(md.filepath(), idx);
582 }
583
584 if( (!libraryPath.isEmpty()) &&
585 (!md.filepath().contains(libraryPath)) )
586 {
587 toDelete << md.id();
588 }
589
590 idx++;
591 }
592
593 bool success;
594 spLog(Log::Debug, this) << "Will delete " << toDelete.size() << " double-tracks";
595 success = deleteTracks(toDelete);
596 spLog(Log::Debug, this) << "delete tracks: " << success;
597
598 success = deleteTracks(doubleMetadata);
599 spLog(Log::Debug, this) << "delete other tracks: " << success;
600
601 return false;
602 }
603
getAllGenres() const604 Util::Set<Genre> Tracks::getAllGenres() const
605 {
606 Query q = module()->runQuery("SELECT genre FROM " + trackView() + " GROUP BY genre;", "Cannot fetch genres");
607
608 if(q.hasError()){
609 return Util::Set<Genre>();
610 }
611
612 Util::Set<Genre> genres;
613 while(q.next())
614 {
615 QString genre = q.value(0).toString();
616 QStringList subgenres = genre.split(",");
617
618 for(const QString& g : subgenres){
619 genres.insert( Genre(g) );
620 }
621 }
622
623 spLog(Log::Debug, this) << "Load all genres finished";
624 return genres;
625 }
626
updateTrackCissearch()627 void Tracks::updateTrackCissearch()
628 {
629 MetaDataList tracks;
630 getAllTracks(tracks);
631
632 module()->db().transaction();
633
634 for(const MetaData& md : tracks)
635 {
636 const QString cis = LibraryUtils::convertSearchstring(md.title());
637 const QString cisFilepaths = LibraryUtils::convertSearchstring(md.filepath());
638 const QString cisGenres = LibraryUtils::convertSearchstring(md.genresToString());
639
640 module()->update("tracks",
641 {
642 {"cissearch", cis},
643 {"fileCissearch", cisFilepaths},
644 {"genreCissearch", cisGenres}
645 },
646 {"trackId", md.id()},
647 "Cannot update album cissearch"
648 );
649 }
650
651 module()->db().commit();
652 }
653
deleteAllTracks(bool also_views)654 void Tracks::deleteAllTracks(bool also_views)
655 {
656 if(libraryId() >= 0)
657 {
658 if(also_views)
659 {
660 dropTrackView(module(), libraryId(), trackView());
661 dropSearchView(module(), trackSearchView());
662 }
663
664 module()->runQuery
665 (
666 "DELETE FROM tracks WHERE libraryId=:libraryId;",
667 {":libraryId", libraryId()},
668 "Cannot delete library tracks"
669 );
670 }
671 }
672
updateTrack(const MetaData & md)673 bool Tracks::updateTrack(const MetaData& md)
674 {
675 if(md.id() < 0 || md.albumId() < 0 || md.artistId() < 0 || md.libraryId() < 0)
676 {
677 spLog(Log::Warning, this) << "Cannot update track (value negative): "
678 << " ArtistID: " << md.artistId()
679 << " AlbumID: " << md.albumId()
680 << " TrackID: " << md.id()
681 << " LibraryID: " << md.libraryId();
682 return false;
683 }
684
685 const QString cissearch = LibraryUtils::convertSearchstring(md.title());
686 const QString fileCissearch = LibraryUtils::convertSearchstring(md.filepath());
687 const QString genreCissearch = LibraryUtils::convertSearchstring(md.genresToString());
688
689 const QString filepath = Util::File::cleanFilename(md.filepath());
690
691 QMap<QString, QVariant> bindings
692 {
693 {"albumArtistID", md.albumArtistId()},
694 {"albumID", md.albumId()},
695 {"artistID", md.artistId()},
696 {"bitrate", md.bitrate()},
697 {"cissearch", cissearch},
698 {"discnumber", md.discnumber()},
699 {"filecissearch", fileCissearch},
700 {"filename", filepath},
701 {"filesize", QVariant::fromValue(md.filesize())},
702 {"genre", Util::convertNotNull(md.genresToString())},
703 {"genreCissearch", genreCissearch},
704 {"length", QVariant::fromValue(md.durationMs())},
705 {"libraryID", md.libraryId()},
706 {"modifydate", QVariant::fromValue(Util::currentDateToInt())},
707 {"rating", QVariant(int(md.rating()))},
708 {"title", Util::convertNotNull(md.title())},
709 {"track", md.trackNumber()},
710 {"year", md.year()},
711 {"comment", Util::convertNotNull(md.comment())}
712 };
713
714 Query q = module()->update("tracks", bindings, {"trackId", md.id()}, QString("Cannot update track %1").arg(md.filepath()));
715
716 return (!q.hasError());
717 }
718
updateTracks(const MetaDataList & tracks)719 bool Tracks::updateTracks(const MetaDataList& tracks)
720 {
721 module()->db().transaction();
722
723 int fileCount = Util::Algorithm::count(tracks, [=](const MetaData& md){
724 return updateTrack(md);
725 });
726
727 bool success = module()->db().commit();
728
729 return success && (fileCount == tracks.count());
730 }
731
732
renameFilepaths(const QMap<QString,QString> & paths,LibraryId targetLibrary)733 bool Tracks::renameFilepaths(const QMap<QString, QString>& paths, LibraryId targetLibrary)
734 {
735 module()->db().transaction();
736
737 const QStringList originalPaths(paths.keys());
738 for(const QString& originalPath : originalPaths)
739 {
740 MetaDataList tracks;
741 getAllTracksByPaths({originalPath}, tracks);
742
743 const QString newPath = paths[originalPath];
744 for(const MetaData& md : tracks)
745 {
746 QString oldFilepath = md.filepath();
747 QString newFilepath(oldFilepath);
748
749 newFilepath.replace(originalPath, newPath);
750 renameFilepath(oldFilepath, newFilepath, targetLibrary);
751 }
752 }
753
754 return module()->db().commit();
755 }
756
renameFilepath(const QString & oldPath,const QString & newPath,LibraryId targetLibrary)757 bool Tracks::renameFilepath(const QString& oldPath, const QString& newPath, LibraryId targetLibrary)
758 {
759 Query q = module()->update
760 (
761 "Tracks",
762 {
763 {"filename", newPath},
764 {"libraryID", targetLibrary}
765 },
766
767 {"filename", oldPath},
768 "Could not rename Filepath"
769 );
770
771 return (!q.hasError());
772 }
773
insertTrackIntoDatabase(const MetaData & md,ArtistId artistId,AlbumId albumId)774 bool Tracks::insertTrackIntoDatabase(const MetaData& md, ArtistId artistId, AlbumId albumId)
775 {
776 return insertTrackIntoDatabase(md, artistId, albumId, artistId);
777 }
778
insertTrackIntoDatabase(const MetaData & md,ArtistId artistId,AlbumId albumId,ArtistId albumArtistId)779 bool Tracks::insertTrackIntoDatabase(const MetaData& md, ArtistId artistId, AlbumId albumId, ArtistId albumArtistId)
780 {
781 if(albumArtistId == -1)
782 {
783 albumArtistId = artistId;
784 }
785
786 uint64_t currentTime = Util::currentDateToInt();
787 uint64_t modifiedTime = md.modifiedDate();
788 uint64_t createdTime = md.createdDate();
789
790 if(!md.createdDateTime().isValid()){
791 createdTime = currentTime;
792 }
793
794 if(!md.modifiedDateTime().isValid()){
795 modifiedTime = currentTime;
796 }
797
798 const QString cissearch = ::Library::Utils::convertSearchstring(md.title());
799 const QString fileCissearch = ::Library::Utils::convertSearchstring(md.filepath());
800 const QString genreCissearch = ::Library::Utils::convertSearchstring(md.genresToString());
801 const QString filepath = Util::File::cleanFilename(md.filepath());
802
803 QMap<QString, QVariant> bindings =
804 {
805 {"filename", filepath},
806 {"albumID", albumId},
807 {"artistID", artistId},
808 {"albumArtistID", albumArtistId},
809 {"title", Util::convertNotNull(md.title())},
810 {"year", md.year()},
811 {"length", QVariant::fromValue(md.durationMs())},
812 {"track", md.trackNumber()},
813 {"bitrate", md.bitrate()},
814 {"genre", Util::convertNotNull(md.genresToString())},
815 {"filesize", QVariant::fromValue(md.filesize())},
816 {"discnumber", md.discnumber()},
817 {"rating", QVariant(int(md.rating()))},
818 {"comment", Util::convertNotNull(md.comment())},
819 {"cissearch", cissearch},
820 {"fileCissearch", fileCissearch},
821 {"genreCissearch", genreCissearch},
822 {"createdate", QVariant::fromValue(createdTime)},
823 {"modifydate", QVariant::fromValue(modifiedTime)},
824 {"libraryID", md.libraryId()}
825 };
826
827 Query q = module()->insert("tracks", bindings, QString("Cannot insert track %1").arg(md.filepath()));
828
829 return (!q.hasError());
830 }
831
832
getFilterClause(const Filter & filter,QString cisPlaceholder)833 static QString getFilterClause(const Filter& filter, QString cisPlaceholder)
834 {
835 cisPlaceholder.remove(":");
836
837 switch( filter.mode() )
838 {
839 case Filter::Genre:
840 if(filter.isInvalidGenre())
841 {
842 return "genre = ''";
843 }
844
845 return "genreCissearch LIKE :" + cisPlaceholder;
846
847 case Filter::Filename:
848 return "fileCissearch LIKE :" + cisPlaceholder;
849
850 case Filter::Fulltext:
851 case Filter::Invalid:
852 default:
853 return "allCissearch LIKE :" + cisPlaceholder;
854 }
855 }
856