diff --git a/src/databaseinterface.h b/src/databaseinterface.h --- a/src/databaseinterface.h +++ b/src/databaseinterface.h @@ -59,6 +59,8 @@ DataTypes::ListRadioDataType allRadiosData(); + DataTypes::ListTrackDataType allTracksDataByArtist(const QString &artistName); + DataTypes::ListTrackDataType recentlyPlayedTracksData(int count); DataTypes::ListTrackDataType frequentlyPlayedTracksData(int count); @@ -91,6 +93,8 @@ qulonglong trackIdFromFileName(const QUrl &fileName); + bool existsTracksFromArtist(const QString &artistName); + void applicationAboutToQuit(); Q_SIGNALS: @@ -254,7 +258,7 @@ DataTypes::AlbumDataType internalOneAlbumPartialData(qulonglong databaseId); - DataTypes::ListTrackDataType internalAllTracksPartialData(); + DataTypes::ListTrackDataType internalAllTracksPartialData(QSqlQuery &query); DataTypes::ListRadioDataType internalAllRadiosPartialData(); diff --git a/src/databaseinterface.cpp b/src/databaseinterface.cpp --- a/src/databaseinterface.cpp +++ b/src/databaseinterface.cpp @@ -89,7 +89,8 @@ mClearTracksTable(mTracksDatabase), mClearAlbumsTable(mTracksDatabase), mClearArtistsTable(mTracksDatabase), mClearComposerTable(mTracksDatabase), mClearGenreTable(mTracksDatabase), mClearLyricistTable(mTracksDatabase), mArtistMatchGenreQuery(mTracksDatabase), mSelectTrackIdQuery(mTracksDatabase), - mInsertRadioQuery(mTracksDatabase), mDeleteRadioQuery(mTracksDatabase) + mInsertRadioQuery(mTracksDatabase), mDeleteRadioQuery(mTracksDatabase), + mSelectAllTracksFromArtistQuery(mTracksDatabase), mExistsTracksFromArtistQuery(mTracksDatabase) { } @@ -265,6 +266,10 @@ QSqlQuery mDeleteRadioQuery; + QSqlQuery mSelectAllTracksFromArtistQuery; + + QSqlQuery mExistsTracksFromArtistQuery; + QSet mModifiedTrackIds; QSet mModifiedAlbumIds; @@ -371,7 +376,32 @@ return result; } - result = internalAllTracksPartialData(); + result = internalAllTracksPartialData(d->mSelectAllTracksQuery); + + transactionResult = finishTransaction(); + if (!transactionResult) { + return result; + } + + return result; +} + +DataTypes::ListTrackDataType DatabaseInterface::allTracksDataByArtist(const QString &artistName) +{ + auto result = DataTypes::ListTrackDataType{}; + + if (!d) { + return result; + } + + auto transactionResult = startTransaction(); + if (!transactionResult) { + return result; + } + + d->mSelectAllTracksFromArtistQuery.bindValue(QStringLiteral(":artistName"), artistName); + + result = internalAllTracksPartialData(d->mSelectAllTracksFromArtistQuery); transactionResult = finishTransaction(); if (!transactionResult) { @@ -677,9 +707,9 @@ if (!queryResult || !d->mArtistMatchGenreQuery.isSelect() || !d->mArtistMatchGenreQuery.isActive()) { Q_EMIT databaseError(); - qCDebug(orgKdeElisaDatabase) << "DatabaseInterface::artistMatchGenre" << d->mArtistMatchGenreQuery.lastQuery(); - qCDebug(orgKdeElisaDatabase) << "DatabaseInterface::artistMatchGenre" << d->mArtistMatchGenreQuery.boundValues(); - qCDebug(orgKdeElisaDatabase) << "DatabaseInterface::artistMatchGenre" << d->mArtistMatchGenreQuery.lastError(); + qCDebug(orgKdeElisaDatabase) << "DatabaseInterface::internalArtistMatchGenre" << d->mArtistMatchGenreQuery.lastQuery(); + qCDebug(orgKdeElisaDatabase) << "DatabaseInterface::internalArtistMatchGenre" << d->mArtistMatchGenreQuery.boundValues(); + qCDebug(orgKdeElisaDatabase) << "DatabaseInterface::internalArtistMatchGenre" << d->mArtistMatchGenreQuery.lastError(); d->mArtistMatchGenreQuery.finish(); @@ -695,8 +725,6 @@ d->mArtistMatchGenreQuery.finish(); - qCDebug(orgKdeElisaDatabase) << "DatabaseInterface::internalArtistMatchGenre" << databaseId << (result ? "match" : "does not match"); - return result; } @@ -766,7 +794,7 @@ } qulonglong DatabaseInterface::trackIdFromTitleAlbumTrackDiscNumber(const QString &title, const QString &artist, const std::optional &album, - std::optional trackNumber, std::optional discNumber) + std::optional trackNumber, std::optional discNumber) { auto result = qulonglong(0); @@ -812,6 +840,49 @@ return result; } +bool DatabaseInterface::existsTracksFromArtist(const QString &artistName) +{ + auto result = false; + + if (!d) { + return result; + } + + auto transactionResult = startTransaction(); + if (!transactionResult) { + return result; + } + + d->mExistsTracksFromArtistQuery.bindValue(QStringLiteral(":artistName"), artistName); + + auto queryResult = execQuery(d->mExistsTracksFromArtistQuery); + + if (!queryResult || !d->mExistsTracksFromArtistQuery.isSelect() || !d->mExistsTracksFromArtistQuery.isActive()) { + Q_EMIT databaseError(); + + qCDebug(orgKdeElisaDatabase) << "DatabaseInterface::existsTracksFromArtist" << d->mExistsTracksFromArtistQuery.lastQuery(); + qCDebug(orgKdeElisaDatabase) << "DatabaseInterface::existsTracksFromArtist" << d->mExistsTracksFromArtistQuery.boundValues(); + qCDebug(orgKdeElisaDatabase) << "DatabaseInterface::existsTracksFromArtist" << d->mExistsTracksFromArtistQuery.lastError(); + + d->mExistsTracksFromArtistQuery.finish(); + + return result; + } + + if (d->mExistsTracksFromArtistQuery.next()) { + result = d->mExistsTracksFromArtistQuery.record().value(0).toBool(); + } + + d->mExistsTracksFromArtistQuery.finish(); + + transactionResult = finishTransaction(); + if (!transactionResult) { + return result; + } + + return result; +} + void DatabaseInterface::applicationAboutToQuit() { d->mStopRequest = 1; @@ -2869,14 +2940,14 @@ //Find webradios (french): https://doc.ubuntu-fr.org/liste_radio_france //English: https://www.radio.fr/language/english (to get the link play a radio and look for streamUrl in the html elements page). const auto &result = createSchemaQuery.exec(QStringLiteral("INSERT INTO `Radios` (`HttpAddress`, `Priority`, `Title`) " - "SELECT 'http://classicrock.stream.ouifm.fr/ouifm3.mp3', 1, 'OuiFM_Classic_Rock' UNION ALL " - "SELECT 'http://rock70s.stream.ouifm.fr/ouifmseventies.mp3', 1, 'OuiFM_70s' UNION ALL " - "SELECT 'http://jazzradio.ice.infomaniak.ch/jazzradio-high.mp3', 2 , 'Jazz_Radio' UNION ALL " - "SELECT 'http://cdn.nrjaudio.fm/audio1/fr/30601/mp3_128.mp3?origine=playerweb', 1, 'Nostalgie' UNION ALL " - "SELECT 'https://scdn.nrjaudio.fm/audio1/fr/30713/aac_64.mp3?origine=playerweb', 1, 'Nostalgie Johnny' UNION ALL " - "SELECT 'http://sc-classrock.1.fm:8200', 1, 'Classic rock replay' UNION ALL " - "SELECT 'http://agnes.torontocast.com:8151/stream', 1, 'Instrumentals Forever' UNION ALL " - "SELECT 'https://stream.laut.fm/jahfari', 1, 'Jahfari'" + "SELECT 'http://classicrock.stream.ouifm.fr/ouifm3.mp3', 1, 'OuiFM_Classic_Rock' UNION ALL " + "SELECT 'http://rock70s.stream.ouifm.fr/ouifmseventies.mp3', 1, 'OuiFM_70s' UNION ALL " + "SELECT 'http://jazzradio.ice.infomaniak.ch/jazzradio-high.mp3', 2 , 'Jazz_Radio' UNION ALL " + "SELECT 'http://cdn.nrjaudio.fm/audio1/fr/30601/mp3_128.mp3?origine=playerweb', 1, 'Nostalgie' UNION ALL " + "SELECT 'https://scdn.nrjaudio.fm/audio1/fr/30713/aac_64.mp3?origine=playerweb', 1, 'Nostalgie Johnny' UNION ALL " + "SELECT 'http://sc-classrock.1.fm:8200', 1, 'Classic rock replay' UNION ALL " + "SELECT 'http://agnes.torontocast.com:8151/stream', 1, 'Instrumentals Forever' UNION ALL " + "SELECT 'https://stream.laut.fm/jahfari', 1, 'Jahfari'" )); if (!result) { qCDebug(orgKdeElisaDatabase) << "DatabaseInterface::initRequest" << createSchemaQuery.lastQuery(); @@ -2959,8 +3030,8 @@ void DatabaseInterface::checkAlbumsTableSchema() { auto fieldsList = QStringList{QStringLiteral("ID"), QStringLiteral("Title"), - QStringLiteral("ArtistName"), QStringLiteral("AlbumPath"), - QStringLiteral("CoverFileName")}; + QStringLiteral("ArtistName"), QStringLiteral("AlbumPath"), + QStringLiteral("CoverFileName")}; genericCheckTable(QStringLiteral("Albums"), fieldsList); } @@ -2996,25 +3067,25 @@ void DatabaseInterface::checkTracksTableSchema() { auto fieldsList = QStringList{QStringLiteral("ID"), QStringLiteral("FileName"), - QStringLiteral("Priority"), QStringLiteral("Title"), - QStringLiteral("ArtistName"), QStringLiteral("AlbumTitle"), - QStringLiteral("AlbumArtistName"), QStringLiteral("AlbumPath"), - QStringLiteral("TrackNumber"), QStringLiteral("DiscNumber"), - QStringLiteral("Duration"), QStringLiteral("Rating"), - QStringLiteral("Genre"), QStringLiteral("Composer"), - QStringLiteral("Lyricist"), QStringLiteral("Comment"), - QStringLiteral("Year"), QStringLiteral("Channels"), - QStringLiteral("BitRate"), QStringLiteral("SampleRate"), - QStringLiteral("HasEmbeddedCover")}; + QStringLiteral("Priority"), QStringLiteral("Title"), + QStringLiteral("ArtistName"), QStringLiteral("AlbumTitle"), + QStringLiteral("AlbumArtistName"), QStringLiteral("AlbumPath"), + QStringLiteral("TrackNumber"), QStringLiteral("DiscNumber"), + QStringLiteral("Duration"), QStringLiteral("Rating"), + QStringLiteral("Genre"), QStringLiteral("Composer"), + QStringLiteral("Lyricist"), QStringLiteral("Comment"), + QStringLiteral("Year"), QStringLiteral("Channels"), + QStringLiteral("BitRate"), QStringLiteral("SampleRate"), + QStringLiteral("HasEmbeddedCover")}; genericCheckTable(QStringLiteral("Tracks"), fieldsList); } void DatabaseInterface::checkTracksDataTableSchema() { auto fieldsList = QStringList{QStringLiteral("FileName"), QStringLiteral("FileModifiedTime"), - QStringLiteral("ImportDate"), QStringLiteral("FirstPlayDate"), - QStringLiteral("LastPlayDate"), QStringLiteral("PlayCounter")}; + QStringLiteral("ImportDate"), QStringLiteral("FirstPlayDate"), + QStringLiteral("LastPlayDate"), QStringLiteral("PlayCounter")}; genericCheckTable(QStringLiteral("TracksData"), fieldsList); } @@ -3167,6 +3238,23 @@ } } + { + auto existsTracksFromArtistText = QStringLiteral("SELECT count(*) " + "FROM `Tracks` track " + "WHERE " + "track.`ArtistName` = :artistName OR " + "track.`AlbumArtistName` = :artistName"); + + auto result = prepareQuery(d->mExistsTracksFromArtistQuery, existsTracksFromArtistText); + + if (!result) { + qCDebug(orgKdeElisaDatabase) << "DatabaseInterface::initRequest" << d->mExistsTracksFromArtistQuery.lastQuery(); + qCDebug(orgKdeElisaDatabase) << "DatabaseInterface::initRequest" << d->mExistsTracksFromArtistQuery.lastError(); + + Q_EMIT databaseError(); + } + } + { auto selectAllAlbumsText = QStringLiteral("SELECT " "album.`ID`, " @@ -3647,6 +3735,102 @@ } } + { + auto selectAllTracksFromArtistText = QStringLiteral("SELECT " + "tracks.`ID`, " + "tracks.`Title`, " + "album.`ID`, " + "tracks.`ArtistName`, " + "tracks.`AlbumArtistName`, " + "tracksMapping.`FileName`, " + "tracksMapping.`FileModifiedTime`, " + "tracks.`TrackNumber`, " + "tracks.`DiscNumber`, " + "tracks.`Duration`, " + "tracks.`AlbumTitle`, " + "tracks.`Rating`, " + "album.`CoverFileName`, " + "(" + "SELECT " + "COUNT(DISTINCT tracks2.DiscNumber) <= 1 " + "FROM " + "`Tracks` tracks2 " + "WHERE " + "tracks2.`AlbumTitle` = album.`Title` AND " + "(tracks2.`AlbumArtistName` = album.`ArtistName` OR " + "(tracks2.`AlbumArtistName` IS NULL AND " + "album.`ArtistName` IS NULL" + ")" + ") AND " + "tracks2.`AlbumPath` = album.`AlbumPath` " + ") as `IsSingleDiscAlbum`, " + "trackGenre.`Name`, " + "trackComposer.`Name`, " + "trackLyricist.`Name`, " + "tracks.`Comment`, " + "tracks.`Year`, " + "tracks.`Channels`, " + "tracks.`BitRate`, " + "tracks.`SampleRate`, " + "tracks.`HasEmbeddedCover`, " + "tracksMapping.`ImportDate`, " + "tracksMapping.`FirstPlayDate`, " + "tracksMapping.`LastPlayDate`, " + "tracksMapping.`PlayCounter`, " + "tracksMapping.`PlayCounter` / (strftime('%s', 'now') - tracksMapping.`FirstPlayDate`) as PlayFrequency, " + "( " + "SELECT tracksCover.`FileName` " + "FROM " + "`Tracks` tracksCover " + "WHERE " + "tracksCover.`HasEmbeddedCover` = 1 AND " + "tracksCover.`AlbumTitle` = album.`Title` AND " + "(tracksCover.`AlbumArtistName` = album.`ArtistName` OR " + "(tracksCover.`AlbumArtistName` IS NULL AND " + "album.`ArtistName` IS NULL " + ") " + ") AND " + "tracksCover.`AlbumPath` = album.`AlbumPath` " + ") as EmbeddedCover " + "FROM " + "`Tracks` tracks, " + "`TracksData` tracksMapping " + "LEFT JOIN " + "`Albums` album " + "ON " + "tracks.`AlbumTitle` = album.`Title` AND " + "(tracks.`AlbumArtistName` = album.`ArtistName` OR tracks.`AlbumArtistName` IS NULL ) AND " + "tracks.`AlbumPath` = album.`AlbumPath` " + "LEFT JOIN `Genre` trackGenre ON trackGenre.`Name` = tracks.`Genre` " + "LEFT JOIN `Composer` trackComposer ON trackComposer.`Name` = tracks.`Composer` " + "LEFT JOIN `Lyricist` trackLyricist ON trackLyricist.`Name` = tracks.`Lyricist` " + "WHERE " + "tracks.`ArtistName` = :artistName AND " + "tracksMapping.`FileName` = tracks.`FileName` AND " + "tracks.`Priority` = (" + " SELECT " + " MIN(`Priority`) " + " FROM " + " `Tracks` tracks2 " + " WHERE " + " tracks.`Title` = tracks2.`Title` AND " + " (tracks.`ArtistName` IS NULL OR tracks.`ArtistName` = tracks2.`ArtistName`) AND " + " (tracks.`AlbumTitle` IS NULL OR tracks.`AlbumTitle` = tracks2.`AlbumTitle`) AND " + " (tracks.`AlbumArtistName` IS NULL OR tracks.`AlbumArtistName` = tracks2.`AlbumArtistName`) AND " + " (tracks.`AlbumPath` IS NULL OR tracks.`AlbumPath` = tracks2.`AlbumPath`)" + ")" + ""); + + auto result = prepareQuery(d->mSelectAllTracksFromArtistQuery, selectAllTracksFromArtistText); + + if (!result) { + qCDebug(orgKdeElisaDatabase) << "DatabaseInterface::initRequest" << d->mSelectAllTracksFromArtistQuery.lastQuery(); + qCDebug(orgKdeElisaDatabase) << "DatabaseInterface::initRequest" << d->mSelectAllTracksFromArtistQuery.lastError(); + + Q_EMIT databaseError(); + } + } + { auto selectAllTracksText = QStringLiteral("SELECT " "tracks.`ID`, " @@ -4444,18 +4628,18 @@ { auto selectRadioFromIdQueryText = QStringLiteral("SELECT " - "radios.`ID`, " - "radios.`Title`, " - "radios.`HttpAddress`, " - "radios.`Rating`, " - "trackGenre.`Name`, " - "radios.`Comment` " - "FROM " - "`Radios` radios " - "LEFT JOIN `Genre` trackGenre ON trackGenre.`Name` = radios.`Genre` " - "WHERE " - "radios.`ID` = :radioId " - ""); + "radios.`ID`, " + "radios.`Title`, " + "radios.`HttpAddress`, " + "radios.`Rating`, " + "trackGenre.`Name`, " + "radios.`Comment` " + "FROM " + "`Radios` radios " + "LEFT JOIN `Genre` trackGenre ON trackGenre.`Name` = radios.`Genre` " + "WHERE " + "radios.`ID` = :radioId " + ""); auto result = prepareQuery(d->mSelectRadioFromIdQuery, selectRadioFromIdQueryText); @@ -7430,23 +7614,23 @@ return result; } -DataTypes::ListTrackDataType DatabaseInterface::internalAllTracksPartialData() +DataTypes::ListTrackDataType DatabaseInterface::internalAllTracksPartialData(QSqlQuery &query) { auto result = DataTypes::ListTrackDataType{}; - if (!internalGenericPartialData(d->mSelectAllTracksQuery)) { + if (!internalGenericPartialData(query)) { return result; } - while(d->mSelectAllTracksQuery.next()) { - const auto ¤tRecord = d->mSelectAllTracksQuery.record(); + while(query.next()) { + const auto ¤tRecord = query.record(); auto newData = buildTrackDataFromDatabaseRecord(currentRecord); result.push_back(newData); } - d->mSelectAllTracksQuery.finish(); + query.finish(); return result; } diff --git a/src/modeldataloader.cpp b/src/modeldataloader.cpp --- a/src/modeldataloader.cpp +++ b/src/modeldataloader.cpp @@ -191,11 +191,27 @@ switch (dataType) { case ElisaUtils::Album: - Q_EMIT allAlbumsData(d->mDatabase->allAlbumsDataByArtist(artist)); + { + auto result = d->mDatabase->allAlbumsDataByArtist(artist); + + if (d->mDatabase->existsTracksFromArtist(artist)) { + result.push_front(AlbumDataType{{AlbumDataType::key_type::TitleRole, QStringLiteral("All Tracks")}, + {AlbumDataType::key_type::SecondaryTextRole, d->mArtist}, + {AlbumDataType::key_type::ElementTypeRole, ElisaUtils::Track}}); + } + + if (!result.isEmpty()) { + Q_EMIT allAlbumsData(result); + } break; + } + case ElisaUtils::Track: + { + Q_EMIT allTracksData(d->mDatabase->allTracksDataByArtist(artist)); + break; + } case ElisaUtils::Artist: case ElisaUtils::Composer: - case ElisaUtils::Track: case ElisaUtils::Genre: case ElisaUtils::Lyricist: case ElisaUtils::FileName: @@ -414,9 +430,9 @@ auto filteredData = newData; auto new_end = std::remove_if(filteredData.begin(), filteredData.end(), [&](const auto &oneAlbum){ - const auto &allGenres = oneAlbum.genres(); - return oneAlbum.artist() != d->mArtist || !allGenres.contains(d->mGenre); - }); + const auto &allGenres = oneAlbum.genres(); + return oneAlbum.artist() != d->mArtist || !allGenres.contains(d->mGenre); + }); filteredData.erase(new_end, filteredData.end()); Q_EMIT albumsAdded(filteredData); diff --git a/src/models/viewsmodel.cpp b/src/models/viewsmodel.cpp --- a/src/models/viewsmodel.cpp +++ b/src/models/viewsmodel.cpp @@ -178,6 +178,7 @@ case ViewManager::OneArtistFromGenre: case ViewManager::OneAlbumFromArtistAndGenre: case ViewManager::AllArtistsFromGenre: + case ViewManager::AllTracksFromArtist: break; } diff --git a/src/viewmanager.h b/src/viewmanager.h --- a/src/viewmanager.h +++ b/src/viewmanager.h @@ -37,6 +37,7 @@ OneArtist, OneAlbumFromArtist, AllTracks, + AllTracksFromArtist, AllGenres, AllArtistsFromGenre, OneArtistFromGenre, @@ -125,6 +126,8 @@ void openAllTracks(const QString &mainTitle, const QUrl &imageUrl); + void openAllTracksFromArtist(const QString &mainTitle, const QString &artist, const QUrl &imageUrl); + void openAllGenres(const QString &mainTitle, const QUrl &imageUrl); void openFilesBrowser(const QString &mainTitle, const QUrl &imageUrl); @@ -154,6 +157,8 @@ void allTracksViewIsLoaded(); + void allTracksFromArtistViewIsLoaded(); + void allGenresViewIsLoaded(); void allArtistsFromGenreViewIsLoaded(); diff --git a/src/viewmanager.cpp b/src/viewmanager.cpp --- a/src/viewmanager.cpp +++ b/src/viewmanager.cpp @@ -60,6 +60,7 @@ case OneArtistFromGenre: case OneAlbumFromArtistAndGenre: case AllArtistsFromGenre: + case AllTracksFromArtist: break; } } @@ -80,6 +81,8 @@ openAllArtistsFromGenre(innerMainTitle); break; case ElisaUtils::Track: + openAllTracksFromArtist(innerMainTitle, innerSecondaryTitle, innerImage); + break; case ElisaUtils::FileName: case ElisaUtils::Lyricist: case ElisaUtils::Composer: @@ -117,6 +120,9 @@ case ViewsType::AllTracks: allTracksViewIsLoaded(); break; + case ViewsType::AllTracksFromArtist: + allTracksFromArtistViewIsLoaded(); + break; case ViewsType::AllGenres: allGenresViewIsLoaded(); break; @@ -260,6 +266,16 @@ } } +void ViewManager::openAllTracksFromArtist(const QString &mainTitle, const QString &artist, const QUrl &imageUrl) +{ + mTargetView = ViewsType::AllTracksFromArtist; + if (mCurrentView != mTargetView) { + Q_EMIT openListView(mTargetView, ElisaUtils::FilterByArtist, 1, mainTitle, artist, + 0, imageUrl, ElisaUtils::Track, Qt::DisplayRole, + SortOrder::SortAscending, MultipleAlbum, NoDiscHeaders, IsTrack); + } +} + void ViewManager::openAllGenres(const QString &mainTitle, const QUrl &imageUrl) { mTargetView = ViewsType::AllGenres; @@ -378,6 +394,11 @@ mCurrentView = ViewsType::AllTracks; } +void ViewManager::allTracksFromArtistViewIsLoaded() +{ + mCurrentView = ViewsType::AllTracksFromArtist; +} + void ViewManager::allGenresViewIsLoaded() { mCurrentView = ViewsType::AllGenres;