diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 6c97d933..0bb432c4 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,463 +1,479 @@ enable_testing() configure_file(mediaplaylisttestconfig.h.in ${CMAKE_CURRENT_BINARY_DIR}/mediaplaylisttestconfig.h @ONLY) include_directories(${elisa_CURRENT_BINARY_DIR}) include_directories(${elisa_BINARY_DIR}) include_directories(${elisa_BINARY_DIR}/src) set(databaseInterfaceTest_SOURCES ../src/databaseinterface.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp databaseinterfacetest.cpp ) ecm_add_test(${databaseInterfaceTest_SOURCES} TEST_NAME "databaseInterfaceTest" LINK_LIBRARIES Qt5::Test Qt5::Core Qt5::Sql KF5::I18n) target_include_directories(databaseInterfaceTest PRIVATE ${CMAKE_SOURCE_DIR}/src) set(managemediaplayercontrolTest_SOURCES ../src/managemediaplayercontrol.cpp ../src/mediaplaylist.cpp ../src/databaseinterface.cpp ../src/musiclistenersmanager.cpp ../src/elisaapplication.cpp + ../src/allalbumsmodel.cpp + ../src/allartistsmodel.cpp + ../src/alltracksmodel.cpp + ../src/albummodel.cpp ../src/notificationitem.cpp ../src/trackslistener.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp ../src/elisautils.cpp ../src/file/filelistener.cpp ../src/file/localfilelisting.cpp ../src/abstractfile/abstractfilelistener.cpp ../src/abstractfile/abstractfilelisting.cpp managemediaplayercontroltest.cpp ) if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) qt5_add_dbus_interface(managemediaplayercontrolTest_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.fileindexer.xml baloo/fileindexer) qt5_add_dbus_interface(managemediaplayercontrolTest_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.scheduler.xml baloo/scheduler) set(managemediaplayercontrolTest_SOURCES ${managemediaplayercontrolTest_SOURCES} ../src/baloo/baloolistener.cpp ../src/baloo/localbaloofilelisting.cpp ) endif() endif() if (UPNPQT_FOUND) set(managemediaplayercontrolTest_SOURCES ${managemediaplayercontrolTest_SOURCES} ../src/upnp/upnpcontrolcontentdirectory.cpp ../src/upnp/upnpcontentdirectorymodel.cpp ../src/upnp/upnpcontrolconnectionmanager.cpp ../src/upnp/upnpcontrolmediaserver.cpp ../src/upnp/didlparser.cpp ../src/upnp/upnplistener.cpp ../src/upnp/upnpdiscoverallmusic.cpp ) endif() kconfig_add_kcfg_files(managemediaplayercontrolTest_SOURCES ../src/elisa_settings.kcfgc ) set(managemediaplayercontrolTest_SOURCES ${managemediaplayercontrolTest_SOURCES} ../src/elisa_core.kcfg ) ecm_add_test(${managemediaplayercontrolTest_SOURCES} TEST_NAME "managemediaplayercontrolTest" LINK_LIBRARIES Qt5::Test Qt5::Core Qt5::Sql Qt5::Widgets KF5::I18n Qt5::Quick Qt5::Multimedia KF5::ConfigCore KF5::ConfigGui KF5::FileMetaData ) if (KF5Baloo_FOUND) target_link_libraries(managemediaplayercontrolTest KF5::Baloo Qt5::DBus) endif() if (KF5KCMUtils_FOUND) target_link_libraries(managemediaplayercontrolTest KF5::KCMUtils) endif() if (UPNPQT_FOUND) target_link_libraries(managemediaplayercontrolTest Qt5::Xml UPNP::upnpQt Qt5::Network) endif() if (KF5XmlGui_FOUND) target_link_libraries(managemediaplayercontrolTest KF5::XmlGui) endif() target_include_directories(managemediaplayercontrolTest PRIVATE ${CMAKE_SOURCE_DIR}/src) set(manageheaderbarTest_SOURCES ../src/manageheaderbar.cpp ../src/mediaplaylist.cpp ../src/databaseinterface.cpp ../src/musiclistenersmanager.cpp ../src/elisaapplication.cpp + ../src/allalbumsmodel.cpp + ../src/allartistsmodel.cpp + ../src/alltracksmodel.cpp + ../src/albummodel.cpp ../src/notificationitem.cpp ../src/trackslistener.cpp ../src/trackslistener.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp ../src/elisautils.cpp ../src/file/filelistener.cpp ../src/file/localfilelisting.cpp ../src/abstractfile/abstractfilelistener.cpp ../src/abstractfile/abstractfilelisting.cpp manageheaderbartest.cpp ) if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) qt5_add_dbus_interface(manageheaderbarTest_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.fileindexer.xml baloo/fileindexer) qt5_add_dbus_interface(manageheaderbarTest_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.scheduler.xml baloo/scheduler) set(manageheaderbarTest_SOURCES ${manageheaderbarTest_SOURCES} ../src/baloo/baloolistener.cpp ../src/baloo/localbaloofilelisting.cpp ) endif() endif() if (UPNPQT_FOUND) set(manageheaderbarTest_SOURCES ${manageheaderbarTest_SOURCES} ../src/upnp/upnpcontrolcontentdirectory.cpp ../src/upnp/upnpcontentdirectorymodel.cpp ../src/upnp/upnpcontrolconnectionmanager.cpp ../src/upnp/upnpcontrolmediaserver.cpp ../src/upnp/didlparser.cpp ../src/upnp/upnplistener.cpp ../src/upnp/upnpdiscoverallmusic.cpp ) endif() kconfig_add_kcfg_files(manageheaderbarTest_SOURCES ../src/elisa_settings.kcfgc ) set(manageheaderbarTest_SOURCES ${manageheaderbarTest_SOURCES} ../src/elisa_core.kcfg ) ecm_add_test(${manageheaderbarTest_SOURCES} TEST_NAME "manageheaderbarTest" LINK_LIBRARIES Qt5::Test Qt5::Core Qt5::Sql Qt5::Gui Qt5::Widgets KF5::I18n Qt5::Quick Qt5::Multimedia KF5::ConfigCore KF5::ConfigGui KF5::FileMetaData ) if (KF5Baloo_FOUND) target_link_libraries(manageheaderbarTest KF5::Baloo Qt5::DBus) endif() if (KF5KCMUtils_FOUND) target_link_libraries(manageheaderbarTest KF5::KCMUtils) endif() if (UPNPQT_FOUND) target_link_libraries(manageheaderbarTest Qt5::Xml UPNP::upnpQt Qt5::Network) endif() if (KF5XmlGui_FOUND) target_link_libraries(manageheaderbarTest KF5::XmlGui) endif() target_include_directories(manageheaderbarTest PRIVATE ${CMAKE_SOURCE_DIR}/src) set(manageaudioplayerTest_SOURCES ../src/manageaudioplayer.cpp manageaudioplayertest.cpp ) ecm_add_test(${manageaudioplayerTest_SOURCES} TEST_NAME "manageaudioplayerTest" LINK_LIBRARIES Qt5::Test Qt5::Core Qt5::Gui Qt5::Multimedia) target_include_directories(manageaudioplayerTest PRIVATE ${CMAKE_SOURCE_DIR}/src) set(mediaplaylistTest_SOURCES ../src/mediaplaylist.cpp ../src/databaseinterface.cpp ../src/trackslistener.cpp ../src/musiclistenersmanager.cpp + ../src/allalbumsmodel.cpp + ../src/allartistsmodel.cpp + ../src/alltracksmodel.cpp + ../src/albummodel.cpp ../src/elisaapplication.cpp ../src/notificationitem.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp ../src/elisautils.cpp ../src/file/filelistener.cpp ../src/file/localfilelisting.cpp ../src/abstractfile/abstractfilelistener.cpp ../src/abstractfile/abstractfilelisting.cpp modeltest.cpp mediaplaylisttest.cpp ) if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) qt5_add_dbus_interface(mediaplaylistTest_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.fileindexer.xml baloo/fileindexer) qt5_add_dbus_interface(mediaplaylistTest_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.scheduler.xml baloo/scheduler) set(mediaplaylistTest_SOURCES ${mediaplaylistTest_SOURCES} ../src/baloo/baloolistener.cpp ../src/baloo/localbaloofilelisting.cpp ) endif() endif() if (UPNPQT_FOUND) set(mediaplaylistTest_SOURCES ${mediaplaylistTest_SOURCES} ../src/upnp/upnpcontrolcontentdirectory.cpp ../src/upnp/upnpcontentdirectorymodel.cpp ../src/upnp/upnpcontrolconnectionmanager.cpp ../src/upnp/upnpcontrolmediaserver.cpp ../src/upnp/didlparser.cpp ../src/upnp/upnplistener.cpp ../src/upnp/upnpdiscoverallmusic.cpp ) endif() kconfig_add_kcfg_files(mediaplaylistTest_SOURCES ../src/elisa_settings.kcfgc ) set(mediaplaylistTest_SOURCES ${mediaplaylistTest_SOURCES} ../src/elisa_core.kcfg ) ecm_add_test(${mediaplaylistTest_SOURCES} TEST_NAME "mediaplaylistTest" LINK_LIBRARIES Qt5::Test Qt5::Core Qt5::Sql Qt5::Gui Qt5::Widgets KF5::I18n Qt5::Quick Qt5::Multimedia KF5::ConfigCore KF5::ConfigGui KF5::FileMetaData ) if (KF5Baloo_FOUND) target_link_libraries(mediaplaylistTest KF5::Baloo Qt5::DBus) endif() if (KF5KCMUtils_FOUND) target_link_libraries(mediaplaylistTest KF5::KCMUtils) endif() if (UPNPQT_FOUND) target_link_libraries(mediaplaylistTest Qt5::Xml UPNP::upnpQt Qt5::Network) endif() if (KF5XmlGui_FOUND) target_link_libraries(mediaplaylistTest KF5::XmlGui) endif() target_include_directories(mediaplaylistTest PRIVATE ${CMAKE_SOURCE_DIR}/src) set(trackslistenertest_SOURCES ../src/mediaplaylist.cpp ../src/databaseinterface.cpp ../src/trackslistener.cpp ../src/musiclistenersmanager.cpp ../src/elisaapplication.cpp + ../src/allalbumsmodel.cpp + ../src/allartistsmodel.cpp + ../src/alltracksmodel.cpp + ../src/albummodel.cpp ../src/notificationitem.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp ../src/elisautils.cpp ../src/file/filelistener.cpp ../src/file/localfilelisting.cpp ../src/abstractfile/abstractfilelistener.cpp ../src/abstractfile/abstractfilelisting.cpp trackslistenertest.cpp ) if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) qt5_add_dbus_interface(trackslistenertest_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.fileindexer.xml baloo/fileindexer) qt5_add_dbus_interface(trackslistenertest_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.scheduler.xml baloo/scheduler) set(trackslistenertest_SOURCES ${trackslistenertest_SOURCES} ../src/baloo/baloolistener.cpp ../src/baloo/localbaloofilelisting.cpp ) endif() endif() if (UPNPQT_FOUND) set(trackslistenertest_SOURCES ${trackslistenertest_SOURCES} ../src/upnp/upnpcontrolcontentdirectory.cpp ../src/upnp/upnpcontentdirectorymodel.cpp ../src/upnp/upnpcontrolconnectionmanager.cpp ../src/upnp/upnpcontrolmediaserver.cpp ../src/upnp/didlparser.cpp ../src/upnp/upnplistener.cpp ../src/upnp/upnpdiscoverallmusic.cpp ) endif() kconfig_add_kcfg_files(trackslistenertest_SOURCES ../src/elisa_settings.kcfgc ) set(trackslistenertest_SOURCES ${trackslistenertest_SOURCES} ../src/elisa_core.kcfg ) ecm_add_test(${trackslistenertest_SOURCES} TEST_NAME "trackslistenertest" LINK_LIBRARIES Qt5::Test Qt5::Core Qt5::Sql Qt5::Gui Qt5::Widgets KF5::I18n Qt5::Quick Qt5::Multimedia KF5::ConfigCore KF5::ConfigGui KF5::FileMetaData ) if (KF5Baloo_FOUND) target_link_libraries(trackslistenertest KF5::Baloo Qt5::DBus) endif() if (KF5KCMUtils_FOUND) target_link_libraries(trackslistenertest KF5::KCMUtils) endif() if (UPNPQT_FOUND) target_link_libraries(trackslistenertest Qt5::Xml UPNP::upnpQt Qt5::Network) endif() if (KF5XmlGui_FOUND) target_link_libraries(trackslistenertest KF5::XmlGui) endif() target_include_directories(trackslistenertest PRIVATE ${CMAKE_SOURCE_DIR}/src) set(allalbumsmodeltest_SOURCES ../src/databaseinterface.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp ../src/allalbumsmodel.cpp ../src/albummodel.cpp modeltest.cpp allalbumsmodeltest.cpp ) ecm_add_test(${allalbumsmodeltest_SOURCES} TEST_NAME "allalbumsmodeltest" LINK_LIBRARIES Qt5::Test Qt5::Core Qt5::Sql KF5::I18n) target_include_directories(allalbumsmodeltest PRIVATE ${CMAKE_SOURCE_DIR}/src) set(albummodeltest_SOURCES ../src/databaseinterface.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp ../src/albummodel.cpp modeltest.cpp albummodeltest.cpp ) ecm_add_test(${albummodeltest_SOURCES} TEST_NAME "albummodeltest" LINK_LIBRARIES Qt5::Test Qt5::Core Qt5::Sql KF5::I18n) target_include_directories(albummodeltest PRIVATE ${CMAKE_SOURCE_DIR}/src) set(allartistsmodeltest_SOURCES ../src/databaseinterface.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp ../src/allartistsmodel.cpp modeltest.cpp allartistsmodeltest.cpp ) ecm_add_test(${allartistsmodeltest_SOURCES} TEST_NAME "allartistsmodeltest" LINK_LIBRARIES Qt5::Test Qt5::Core Qt5::Sql KF5::I18n) target_include_directories(allartistsmodeltest PRIVATE ${CMAKE_SOURCE_DIR}/src) set(alltracksmodeltest_SOURCES ../src/databaseinterface.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp ../src/alltracksmodel.cpp modeltest.cpp alltracksmodeltest.cpp ) ecm_add_test(${alltracksmodeltest_SOURCES} TEST_NAME "alltracksmodeltest" LINK_LIBRARIES Qt5::Test Qt5::Core Qt5::Sql KF5::I18n) target_include_directories(alltracksmodeltest PRIVATE ${CMAKE_SOURCE_DIR}/src) set(localfilelistingtest_SOURCES ../src/file/localfilelisting.cpp ../src/abstractfile/abstractfilelisting.cpp ../src/musicaudiotrack.cpp ../src/notificationitem.cpp ../src/elisautils.cpp localfilelistingtest.cpp ) kconfig_add_kcfg_files(localfilelistingtest_SOURCES ../src/elisa_settings.kcfgc ) set(localfilelistingtest_SOURCES ${localfilelistingtest_SOURCES} ../src/elisa_core.kcfg ) ecm_add_test(${localfilelistingtest_SOURCES} TEST_NAME "localfilelistingtest" LINK_LIBRARIES Qt5::Test Qt5::Core Qt5::Sql KF5::I18n KF5::FileMetaData KF5::ConfigCore KF5::ConfigGui) target_include_directories(localfilelistingtest PRIVATE ${CMAKE_SOURCE_DIR}/src) if (KF5XmlGui_FOUND AND KF5KCMUtils_FOUND) set(elisaapplicationtest_SOURCES ../src/elisaapplication.cpp elisaapplicationtest.cpp ) kconfig_add_kcfg_files(elisaapplicationtest_SOURCES ../src/elisa_settings.kcfgc ) set(elisaapplicationtest_SOURCES ${elisaapplicationtest_SOURCES} ../src/elisa_core.kcfg ) ecm_add_test(${elisaapplicationtest_SOURCES} TEST_NAME "elisaapplicationtest" LINK_LIBRARIES Qt5::Test Qt5::Core Qt5::Widgets KF5::ConfigCore KF5::ConfigGui KF5::ConfigWidgets KF5::XmlGui KF5::KCMUtils) target_include_directories(elisaapplicationtest PRIVATE ${CMAKE_SOURCE_DIR}/src) endif() if (Qt5Quick_FOUND AND Qt5Widgets_FOUND) set(elisaqmltests_SOURCES elisaqmltests.cpp qmltests/tst_GridBrowserDelegate.qml ) ecm_add_test(${elisaqmltests_SOURCES} TEST_NAME "elisaqmltests" LINK_LIBRARIES Qt5::Core Qt5::Widgets Qt5::Test Qt5::QuickTest GUI) target_compile_definitions(elisaqmltests PRIVATE QUICK_TEST_SOURCE_DIR="${CMAKE_SOURCE_DIR}/autotests/qmltests") target_include_directories(elisaqmltests PRIVATE ${CMAKE_SOURCE_DIR}/src) endif() diff --git a/autotests/localfilelistingtest.cpp b/autotests/localfilelistingtest.cpp index 708c2221..8f3f70d2 100644 --- a/autotests/localfilelistingtest.cpp +++ b/autotests/localfilelistingtest.cpp @@ -1,445 +1,447 @@ /* * Copyright 2015-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "file/localfilelisting.h" #include "musicaudiotrack.h" #include "config-upnp-qt.h" #include #include #include #include #include #include #include #include #include #include #include #include class LocalFileListingTests: public QObject { Q_OBJECT public: LocalFileListingTests(QObject *parent = nullptr) : QObject(parent) { } private: QHash> mNewTracks; QHash mNewCovers; private Q_SLOTS: void initTestCase() { qRegisterMetaType>("QHash"); qRegisterMetaType>("QHash"); qRegisterMetaType>("QList"); qRegisterMetaType>("QVector"); qRegisterMetaType>("QHash"); qRegisterMetaType>("QList"); } void initialTestWithNoTrack() { LocalFileListing myListing; QSignalSpy tracksListSpy(&myListing, &LocalFileListing::tracksList); QSignalSpy removedTracksListSpy(&myListing, &LocalFileListing::removedTracksList); QSignalSpy rootPathChangedSpy(&myListing, &LocalFileListing::rootPathChanged); QCOMPARE(tracksListSpy.count(), 0); QCOMPARE(removedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 0); myListing.init(); QCOMPARE(tracksListSpy.count(), 0); QCOMPARE(removedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 0); myListing.setRootPath(QStringLiteral("/directoryNotExist")); QCOMPARE(tracksListSpy.count(), 0); QCOMPARE(removedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 1); QCOMPARE(myListing.rootPath(), QStringLiteral("/directoryNotExist")); myListing.refreshContent(); QCOMPARE(tracksListSpy.count(), 0); QCOMPARE(removedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 1); } void initialTestWithTracks() { LocalFileListing myListing; QString musicPath = QStringLiteral(LOCAL_FILE_TESTS_SAMPLE_FILES_PATH) + QStringLiteral("/music"); QSignalSpy tracksListSpy(&myListing, &LocalFileListing::tracksList); QSignalSpy removedTracksListSpy(&myListing, &LocalFileListing::removedTracksList); QSignalSpy rootPathChangedSpy(&myListing, &LocalFileListing::rootPathChanged); QCOMPARE(tracksListSpy.count(), 0); QCOMPARE(removedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 0); myListing.init(); QCOMPARE(tracksListSpy.count(), 0); QCOMPARE(removedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 0); myListing.setRootPath(musicPath); QCOMPARE(tracksListSpy.count(), 0); QCOMPARE(removedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 1); QCOMPARE(myListing.rootPath(), musicPath); myListing.refreshContent(); - QCOMPARE(tracksListSpy.count(), 1); + QCOMPARE(tracksListSpy.count(), 2); QCOMPARE(removedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 1); - auto newTracksSignal = tracksListSpy.at(0); - auto newTracks = newTracksSignal.at(0).value>(); - auto newCovers = newTracksSignal.at(1).value>(); + auto firstNewTracksSignal = tracksListSpy.at(0); + auto firstNewTracks = firstNewTracksSignal.at(0).value>(); + auto secondNewTracksSignal = tracksListSpy.at(1); + auto secondNewTracks = secondNewTracksSignal.at(0).value>(); + auto newCovers = secondNewTracksSignal.at(1).value>(); - QCOMPARE(newTracks.count(), 3); + QCOMPARE(firstNewTracks.count() + secondNewTracks.count(), 3); QCOMPARE(newCovers.count(), 3); } void addAndRemoveTracks() { LocalFileListing myListing; QString musicOriginPath = QStringLiteral(LOCAL_FILE_TESTS_SAMPLE_FILES_PATH) + QStringLiteral("/music"); QString musicPath = QStringLiteral(LOCAL_FILE_TESTS_WORKING_PATH) + QStringLiteral("/music2/data/innerData"); QString musicParentPath = QStringLiteral(LOCAL_FILE_TESTS_WORKING_PATH) + QStringLiteral("/music2"); QDir musicParentDirectory(musicParentPath); QDir rootDirectory(QStringLiteral(LOCAL_FILE_TESTS_WORKING_PATH)); musicParentDirectory.removeRecursively(); rootDirectory.mkpath(QStringLiteral("music2/data/innerData")); QSignalSpy tracksListSpy(&myListing, &LocalFileListing::tracksList); QSignalSpy removedTracksListSpy(&myListing, &LocalFileListing::removedTracksList); QSignalSpy rootPathChangedSpy(&myListing, &LocalFileListing::rootPathChanged); QCOMPARE(tracksListSpy.count(), 0); QCOMPARE(removedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 0); myListing.init(); QCOMPARE(tracksListSpy.count(), 0); QCOMPARE(removedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 0); myListing.setRootPath(musicParentPath); QCOMPARE(tracksListSpy.count(), 0); QCOMPARE(removedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 1); QCOMPARE(myListing.rootPath(), musicParentPath); myListing.refreshContent(); QCOMPARE(tracksListSpy.count(), 0); QCOMPARE(removedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 1); QFile myTrack(musicOriginPath + QStringLiteral("/test.ogg")); myTrack.copy(musicPath + QStringLiteral("/test.ogg")); QFile myCover(musicOriginPath + QStringLiteral("/cover.jpg")); myCover.copy(musicPath + QStringLiteral("/cover.jpg")); QCOMPARE(tracksListSpy.wait(), true); QCOMPARE(tracksListSpy.count(), 1); QCOMPARE(removedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 1); auto newTracksSignal = tracksListSpy.at(0); auto newTracks = newTracksSignal.at(0).value>(); auto newCovers = newTracksSignal.at(1).value>(); QCOMPARE(newTracks.count(), 1); QCOMPARE(newCovers.count(), 1); QString commandLine(QStringLiteral("rm -rf ") + musicPath); system(commandLine.toLatin1().data()); QCOMPARE(removedTracksListSpy.wait(), true); QCOMPARE(tracksListSpy.count(), 1); QCOMPARE(removedTracksListSpy.count(), 1); QCOMPARE(rootPathChangedSpy.count(), 1); auto removeSignal = removedTracksListSpy.at(0); auto removedTracks = removeSignal.at(0).value>(); QCOMPARE(removedTracks.isEmpty(), false); QCOMPARE(rootDirectory.mkpath(QStringLiteral("music2/data/innerData")), true); QCOMPARE(myTrack.copy(musicPath + QStringLiteral("/test.ogg")), true); QCOMPARE(myCover.copy(musicPath + QStringLiteral("/cover.jpg")), true); if (tracksListSpy.count() == 1) { QCOMPARE(tracksListSpy.wait(), true); } QCOMPARE(tracksListSpy.count(), 2); QCOMPARE(removedTracksListSpy.count(), 1); QCOMPARE(rootPathChangedSpy.count(), 1); auto newTracksSignalLast = tracksListSpy.at(1); auto newTracksLast = newTracksSignalLast.at(0).value>(); auto newCoversLast = newTracksSignalLast.at(1).value>(); QCOMPARE(newTracksLast.count(), 1); QCOMPARE(newCoversLast.count(), 1); } void addTracksAndRemoveDirectory() { LocalFileListing myListing; QString musicOriginPath = QStringLiteral(LOCAL_FILE_TESTS_SAMPLE_FILES_PATH) + QStringLiteral("/music"); QString musicPath = QStringLiteral(LOCAL_FILE_TESTS_WORKING_PATH) + QStringLiteral("/music2/data/innerData"); QString innerMusicPath = QStringLiteral(LOCAL_FILE_TESTS_WORKING_PATH) + QStringLiteral("/music2/data"); QString musicParentPath = QStringLiteral(LOCAL_FILE_TESTS_WORKING_PATH) + QStringLiteral("/music2"); QDir musicParentDirectory(musicParentPath); QDir rootDirectory(QStringLiteral(LOCAL_FILE_TESTS_WORKING_PATH)); musicParentDirectory.removeRecursively(); rootDirectory.mkpath(QStringLiteral("music2/data/innerData")); QSignalSpy tracksListSpy(&myListing, &LocalFileListing::tracksList); QSignalSpy removedTracksListSpy(&myListing, &LocalFileListing::removedTracksList); QSignalSpy rootPathChangedSpy(&myListing, &LocalFileListing::rootPathChanged); QCOMPARE(tracksListSpy.count(), 0); QCOMPARE(removedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 0); myListing.init(); QCOMPARE(tracksListSpy.count(), 0); QCOMPARE(removedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 0); myListing.setRootPath(musicParentPath); QCOMPARE(tracksListSpy.count(), 0); QCOMPARE(removedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 1); QCOMPARE(myListing.rootPath(), musicParentPath); myListing.refreshContent(); QCOMPARE(tracksListSpy.count(), 0); QCOMPARE(removedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 1); QFile myTrack(musicOriginPath + QStringLiteral("/test.ogg")); myTrack.copy(musicPath + QStringLiteral("/test.ogg")); QFile myCover(musicOriginPath + QStringLiteral("/cover.jpg")); myCover.copy(musicPath + QStringLiteral("/cover.jpg")); QCOMPARE(tracksListSpy.wait(), true); QCOMPARE(tracksListSpy.count(), 1); QCOMPARE(removedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 1); auto newTracksSignal = tracksListSpy.at(0); auto newTracks = newTracksSignal.at(0).value>(); auto newCovers = newTracksSignal.at(1).value>(); QCOMPARE(newTracks.count(), 1); QCOMPARE(newCovers.count(), 1); QString commandLine(QStringLiteral("rm -rf ") + innerMusicPath); system(commandLine.toLatin1().data()); QCOMPARE(removedTracksListSpy.wait(), true); QCOMPARE(tracksListSpy.count(), 1); QCOMPARE(removedTracksListSpy.count(), 1); QCOMPARE(rootPathChangedSpy.count(), 1); auto removeSignal = removedTracksListSpy.at(0); auto removedTracks = removeSignal.at(0).value>(); QCOMPARE(removedTracks.isEmpty(), false); QCOMPARE(rootDirectory.mkpath(QStringLiteral("music2/data/innerData")), true); QCOMPARE(myTrack.copy(musicPath + QStringLiteral("/test.ogg")), true); QCOMPARE(myCover.copy(musicPath + QStringLiteral("/cover.jpg")), true); if (tracksListSpy.count() == 1) { QCOMPARE(tracksListSpy.wait(), true); } QCOMPARE(tracksListSpy.count(), 2); QCOMPARE(removedTracksListSpy.count(), 1); QCOMPARE(rootPathChangedSpy.count(), 1); auto newTracksSignalLast = tracksListSpy.at(1); auto newTracksLast = newTracksSignalLast.at(0).value>(); auto newCoversLast = newTracksSignalLast.at(1).value>(); QCOMPARE(newTracksLast.count(), 1); QCOMPARE(newCoversLast.count(), 1); } void addAndMoveTracks() { LocalFileListing myListing; QString musicOriginPath = QStringLiteral(LOCAL_FILE_TESTS_SAMPLE_FILES_PATH) + QStringLiteral("/music"); QString musicPath = QStringLiteral(LOCAL_FILE_TESTS_WORKING_PATH) + QStringLiteral("/music2/data/innerData"); QDir musicDirectory(musicPath); QString musicParentPath = QStringLiteral(LOCAL_FILE_TESTS_WORKING_PATH) + QStringLiteral("/music2"); QDir musicParentDirectory(musicParentPath); QString musicFriendPath = QStringLiteral(LOCAL_FILE_TESTS_WORKING_PATH) + QStringLiteral("/music3"); QDir musicFriendDirectory(musicFriendPath); QCOMPARE(musicFriendDirectory.removeRecursively(), true); QCOMPARE(musicParentDirectory.removeRecursively(), true); musicFriendDirectory.mkpath(musicFriendPath); musicDirectory.mkpath(musicPath); QSignalSpy tracksListSpy(&myListing, &LocalFileListing::tracksList); QSignalSpy removedTracksListSpy(&myListing, &LocalFileListing::removedTracksList); QSignalSpy modifiedTracksListSpy(&myListing, &LocalFileListing::modifyTracksList); QSignalSpy rootPathChangedSpy(&myListing, &LocalFileListing::rootPathChanged); QCOMPARE(tracksListSpy.count(), 0); QCOMPARE(removedTracksListSpy.count(), 0); QCOMPARE(modifiedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 0); myListing.init(); QCOMPARE(tracksListSpy.count(), 0); QCOMPARE(removedTracksListSpy.count(), 0); QCOMPARE(modifiedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 0); myListing.setRootPath(musicParentPath); QCOMPARE(tracksListSpy.count(), 0); QCOMPARE(removedTracksListSpy.count(), 0); QCOMPARE(modifiedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 1); QCOMPARE(myListing.rootPath(), musicParentPath); myListing.refreshContent(); QCOMPARE(tracksListSpy.count(), 0); QCOMPARE(removedTracksListSpy.count(), 0); QCOMPARE(modifiedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 1); QFile myTrack(musicOriginPath + QStringLiteral("/test.ogg")); myTrack.copy(musicPath + QStringLiteral("/test.ogg")); QFile myCover(musicOriginPath + QStringLiteral("/cover.jpg")); myCover.copy(musicPath + QStringLiteral("/cover.jpg")); QCOMPARE(tracksListSpy.wait(), true); QCOMPARE(tracksListSpy.count(), 1); QCOMPARE(removedTracksListSpy.count(), 0); QCOMPARE(modifiedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 1); auto newTracksSignal = tracksListSpy.at(0); auto newTracks = newTracksSignal.at(0).value>(); auto newCovers = newTracksSignal.at(1).value>(); QCOMPARE(newTracks.count(), 1); QCOMPARE(newCovers.count(), 1); QString commandLine(QStringLiteral("mv ") + musicPath + QStringLiteral(" ") + musicFriendPath); system(commandLine.toLatin1().data()); QCOMPARE(removedTracksListSpy.wait(), true); QCOMPARE(tracksListSpy.count(), 1); QCOMPARE(removedTracksListSpy.count(), 1); QCOMPARE(modifiedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 1); auto removeSignal = removedTracksListSpy.at(0); auto removedTracks = removeSignal.at(0).value>(); QCOMPARE(removedTracks.isEmpty(), false); QCOMPARE(musicFriendDirectory.mkpath(musicFriendPath), true); QCOMPARE(musicDirectory.mkpath(musicPath), true); QCOMPARE(myTrack.copy(musicPath + QStringLiteral("/test.ogg")), true); QCOMPARE(myCover.copy(musicPath + QStringLiteral("/cover.jpg")), true); if (tracksListSpy.count() == 1) { QCOMPARE(tracksListSpy.wait(), true); } QCOMPARE(tracksListSpy.count(), 2); QCOMPARE(removedTracksListSpy.count(), 1); QCOMPARE(modifiedTracksListSpy.count(), 0); QCOMPARE(rootPathChangedSpy.count(), 1); auto newTracksSignalLast = tracksListSpy.at(1); auto newTracksLast = newTracksSignalLast.at(0).value>(); auto newCoversLast = newTracksSignalLast.at(1).value>(); QCOMPARE(newTracksLast.count(), 1); QCOMPARE(newCoversLast.count(), 1); } }; QTEST_GUILESS_MAIN(LocalFileListingTests) #include "localfilelistingtest.moc" diff --git a/src/abstractfile/abstractfilelistener.h b/src/abstractfile/abstractfilelistener.h index 2c02c463..2f1ba655 100644 --- a/src/abstractfile/abstractfilelistener.h +++ b/src/abstractfile/abstractfilelistener.h @@ -1,104 +1,104 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ABSTRACTFILELISTENER_H #define ABSTRACTFILELISTENER_H #include "notificationitem.h" #include #include #include #include class AbstractFileListenerPrivate; class DatabaseInterface; class MusicAudioTrack; class AbstractFileListing; class AbstractFileListener : public QObject { Q_OBJECT Q_PROPERTY(DatabaseInterface* databaseInterface READ databaseInterface WRITE setDatabaseInterface NOTIFY databaseInterfaceChanged) Q_PROPERTY(int importedTracksCount READ importedTracksCount NOTIFY importedTracksCountChanged) public: explicit AbstractFileListener(QObject *parent = nullptr); ~AbstractFileListener() override; DatabaseInterface* databaseInterface() const; AbstractFileListing* fileListing() const; int importedTracksCount() const; Q_SIGNALS: void databaseInterfaceChanged(); void newTrackFile(const MusicAudioTrack &newTrack); void indexingStarted(); - void indexingFinished(); + void indexingFinished(int tracksCount); void configurationChanged(); void clearDatabase(const QString &listenerName); void importedTracksCountChanged(); void newNotification(NotificationItem notification); void closeNotification(QString notificationId); public Q_SLOTS: void performInitialScan(); void setDatabaseInterface(DatabaseInterface* databaseInterface); void applicationAboutToQuit(); void quitListener(); void resetImportedTracksCounter(); protected: void setFileListing(AbstractFileListing *fileIndexer); NotificationItem& currentNotification(); private: std::unique_ptr d; }; #endif // ABSTRACTFILELISTENER_H diff --git a/src/abstractfile/abstractfilelisting.cpp b/src/abstractfile/abstractfilelisting.cpp index 2074b945..ec58e78d 100644 --- a/src/abstractfile/abstractfilelisting.cpp +++ b/src/abstractfile/abstractfilelisting.cpp @@ -1,398 +1,403 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "abstractfilelisting.h" #include "musicaudiotrack.h" #include "notificationitem.h" #include "elisautils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include + #include #include #include class AbstractFileListingPrivate { public: explicit AbstractFileListingPrivate(QString sourceName) : mSourceName(std::move(sourceName)) { } QFileSystemWatcher mFileSystemWatcher; QHash mAllAlbumCover; QHash>> mDiscoveredFiles; QString mSourceName; bool mHandleNewFiles = true; KFileMetaData::ExtractorCollection mExtractors; QAtomicInt mStopRequest = 0; QMimeDatabase mMimeDb; int mImportedTracksCount = 0; + int mNotificationUpdateInterval = 1; + + int mNewFilesEmitInterval = 1; + }; AbstractFileListing::AbstractFileListing(const QString &sourceName, QObject *parent) : QObject(parent), d(std::make_unique(sourceName)) { connect(&d->mFileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, &AbstractFileListing::directoryChanged); connect(&d->mFileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &AbstractFileListing::fileChanged); } AbstractFileListing::~AbstractFileListing() = default; void AbstractFileListing::init() { executeInit(); } void AbstractFileListing::newTrackFile(const MusicAudioTrack &partialTrack) { const auto &newTrack = scanOneFile(partialTrack.resourceURI()); if (newTrack.isValid() && newTrack != partialTrack) { Q_EMIT modifyTracksList({newTrack}, d->mAllAlbumCover, d->mSourceName); } } void AbstractFileListing::resetImportedTracksCounter() { d->mImportedTracksCount = 0; } void AbstractFileListing::applicationAboutToQuit() { d->mStopRequest = 1; } void AbstractFileListing::scanDirectory(QList &newFiles, const QUrl &path) { if (d->mStopRequest == 1) { return; } QDir rootDirectory(path.toLocalFile()); rootDirectory.refresh(); if (rootDirectory.exists()) { watchPath(path.toLocalFile()); } auto ¤tDirectoryListingFiles = d->mDiscoveredFiles[path]; auto currentFilesList = QSet(); rootDirectory.refresh(); const auto entryList = rootDirectory.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); for (const auto &oneEntry : entryList) { auto newFilePath = QUrl::fromLocalFile(oneEntry.canonicalFilePath()); if (oneEntry.isDir() || oneEntry.isFile()) { currentFilesList.insert(newFilePath); } } auto removedTracks = QVector>(); for (const auto &removedFilePath : currentDirectoryListingFiles) { auto itFilePath = std::find(currentFilesList.begin(), currentFilesList.end(), removedFilePath.first); if (itFilePath != currentFilesList.end()) { continue; } removedTracks.push_back(removedFilePath); } auto allRemovedTracks = QList(); for (const auto &oneRemovedTrack : removedTracks) { if (oneRemovedTrack.second) { allRemovedTracks.push_back(oneRemovedTrack.first); } else { removeFile(oneRemovedTrack.first, allRemovedTracks); } } for (const auto &oneRemovedTrack : removedTracks) { currentDirectoryListingFiles.remove(oneRemovedTrack); currentDirectoryListingFiles.remove(oneRemovedTrack); } if (!allRemovedTracks.isEmpty()) { Q_EMIT removedTracksList(allRemovedTracks); } if (!d->mHandleNewFiles) { return; } for (const auto &newFilePath : currentFilesList) { QFileInfo oneEntry(newFilePath.toLocalFile()); auto itFilePath = std::find(currentDirectoryListingFiles.begin(), currentDirectoryListingFiles.end(), QPair{newFilePath, oneEntry.isFile()}); if (itFilePath != currentDirectoryListingFiles.end()) { continue; } if (oneEntry.isDir()) { addFileInDirectory(newFilePath, path); scanDirectory(newFiles, newFilePath); if (d->mStopRequest == 1) { break; } continue; } if (!oneEntry.isFile()) { continue; } auto newTrack = scanOneFile(newFilePath); if (newTrack.isValid() && d->mStopRequest == 0) { addCover(newTrack); addFileInDirectory(newTrack.resourceURI(), path); newFiles.push_back(newTrack); ++d->mImportedTracksCount; - if (d->mImportedTracksCount % 50 == 0) { + if (d->mImportedTracksCount % d->mNotificationUpdateInterval == 0) { + d->mNotificationUpdateInterval = std::min(50, 1 + d->mNotificationUpdateInterval * 2); Q_EMIT importedTracksCountChanged(); } - if (newFiles.size() > 500 && d->mStopRequest == 0) { + if (newFiles.size() > d->mNewFilesEmitInterval && d->mStopRequest == 0) { + d->mNewFilesEmitInterval = std::min(50, 1 + d->mNewFilesEmitInterval * d->mNewFilesEmitInterval); Q_EMIT importedTracksCountChanged(); emitNewFiles(newFiles); newFiles.clear(); } } if (d->mStopRequest == 1) { Q_EMIT importedTracksCountChanged(); break; } } } const QString &AbstractFileListing::sourceName() const { return d->mSourceName; } int AbstractFileListing::importedTracksCount() const { return d->mImportedTracksCount; } void AbstractFileListing::directoryChanged(const QString &path) { const auto directoryEntry = d->mDiscoveredFiles.find(QUrl::fromLocalFile(path)); if (directoryEntry == d->mDiscoveredFiles.end()) { return; } Q_EMIT indexingStarted(); scanDirectoryTree(path); - Q_EMIT indexingFinished(); + Q_EMIT indexingFinished(d->mImportedTracksCount); } void AbstractFileListing::fileChanged(const QString &modifiedFileName) { auto modifiedFile = QUrl::fromLocalFile(modifiedFileName); auto modifiedTrack = scanOneFile(modifiedFile); if (modifiedTrack.isValid()) { Q_EMIT modifyTracksList({modifiedTrack}, d->mAllAlbumCover, d->mSourceName); } } void AbstractFileListing::executeInit() { } void AbstractFileListing::triggerRefreshOfContent() { d->mImportedTracksCount = 0; } void AbstractFileListing::refreshContent() { triggerRefreshOfContent(); } MusicAudioTrack AbstractFileListing::scanOneFile(const QUrl &scanFile) { MusicAudioTrack newTrack; newTrack = ElisaUtils::scanOneFile(scanFile, d->mMimeDb, d->mExtractors); if (newTrack.isValid()) { QFileInfo scanFileInfo(scanFile.toLocalFile()); if (scanFileInfo.exists()) { watchPath(scanFile.toLocalFile()); } } return newTrack; } void AbstractFileListing::watchPath(const QString &pathName) { d->mFileSystemWatcher.addPath(pathName); } void AbstractFileListing::addFileInDirectory(const QUrl &newFile, const QUrl &directoryName) { const auto directoryEntry = d->mDiscoveredFiles.find(directoryName); if (directoryEntry == d->mDiscoveredFiles.end()) { watchPath(directoryName.toLocalFile()); QDir currentDirectory(directoryName.toLocalFile()); if (currentDirectory.cdUp()) { const auto parentDirectoryName = currentDirectory.absolutePath(); const auto parentDirectory = QUrl::fromLocalFile(parentDirectoryName); const auto parentDirectoryEntry = d->mDiscoveredFiles.find(parentDirectory); if (parentDirectoryEntry == d->mDiscoveredFiles.end()) { watchPath(parentDirectoryName); } auto &parentCurrentDirectoryListingFiles = d->mDiscoveredFiles[parentDirectory]; parentCurrentDirectoryListingFiles.insert({directoryName, false}); } } auto ¤tDirectoryListingFiles = d->mDiscoveredFiles[directoryName]; QFileInfo isAFile(newFile.toLocalFile()); currentDirectoryListingFiles.insert({newFile, isAFile.isFile()}); } void AbstractFileListing::scanDirectoryTree(const QString &path) { auto newFiles = QList(); scanDirectory(newFiles, QUrl::fromLocalFile(path)); if (!newFiles.isEmpty() && d->mStopRequest == 0) { Q_EMIT importedTracksCountChanged(); emitNewFiles(newFiles); } } void AbstractFileListing::setHandleNewFiles(bool handleThem) { d->mHandleNewFiles = handleThem; } void AbstractFileListing::emitNewFiles(const QList &tracks) { - qDebug() << "AbstractFileListing::emitNewFiles" << tracks.size(); Q_EMIT tracksList(tracks, d->mAllAlbumCover, d->mSourceName); } void AbstractFileListing::addCover(const MusicAudioTrack &newTrack) { auto itCover = d->mAllAlbumCover.find(newTrack.albumName()); if (itCover != d->mAllAlbumCover.end()) { return; } QFileInfo trackFilePath(newTrack.resourceURI().toLocalFile()); QFileInfo coverFilePath(trackFilePath.dir().filePath(QStringLiteral("cover.jpg"))); if (coverFilePath.exists()) { d->mAllAlbumCover[newTrack.resourceURI().toString()] = QUrl::fromLocalFile(coverFilePath.absoluteFilePath()); return; } coverFilePath.setFile(trackFilePath.dir().filePath(QStringLiteral("cover.png"))); if (coverFilePath.exists()) { d->mAllAlbumCover[newTrack.resourceURI().toString()] = QUrl::fromLocalFile(coverFilePath.absoluteFilePath()); } } void AbstractFileListing::removeDirectory(const QUrl &removedDirectory, QList &allRemovedFiles) { const auto itRemovedDirectory = d->mDiscoveredFiles.find(removedDirectory); if (itRemovedDirectory == d->mDiscoveredFiles.end()) { return; } const auto ¤tRemovedDirectory = *itRemovedDirectory; for (const auto &itFile : currentRemovedDirectory) { if (itFile.first.isValid() && !itFile.first.isEmpty()) { removeFile(itFile.first, allRemovedFiles); if (itFile.second) { allRemovedFiles.push_back(itFile.first); } } } d->mDiscoveredFiles.erase(itRemovedDirectory); } void AbstractFileListing::removeFile(const QUrl &oneRemovedTrack, QList &allRemovedFiles) { auto itRemovedDirectory = d->mDiscoveredFiles.find(oneRemovedTrack); if (itRemovedDirectory != d->mDiscoveredFiles.end()) { removeDirectory(oneRemovedTrack, allRemovedFiles); } } void AbstractFileListing::setSourceName(const QString &name) { d->mSourceName = name; } void AbstractFileListing::increaseImportedTracksCount() { ++d->mImportedTracksCount; } #include "moc_abstractfilelisting.cpp" diff --git a/src/abstractfile/abstractfilelisting.h b/src/abstractfile/abstractfilelisting.h index 6fef9d03..12d3a8c9 100644 --- a/src/abstractfile/abstractfilelisting.h +++ b/src/abstractfile/abstractfilelisting.h @@ -1,130 +1,130 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ABSTRACTFILELISTING_H #define ABSTRACTFILELISTING_H #include "notificationitem.h" #include #include #include #include #include #include class AbstractFileListingPrivate; class MusicAudioTrack; class NotificationItem; class AbstractFileListing : public QObject { Q_OBJECT Q_PROPERTY(int importedTracksCount READ importedTracksCount NOTIFY importedTracksCountChanged) public: explicit AbstractFileListing(const QString &sourceName, QObject *parent = nullptr); ~AbstractFileListing() override; virtual void applicationAboutToQuit(); const QString &sourceName() const; int importedTracksCount() const; Q_SIGNALS: void tracksList(const QList &tracks, const QHash &covers, const QString &musicSource); void removedTracksList(const QList &removedTracks); void modifyTracksList(const QList &modifiedTracks, const QHash &covers, const QString &musicSource); void indexingStarted(); - void indexingFinished(); + void indexingFinished(int tracksCount); void importedTracksCountChanged(); void newNotification(NotificationItem notification); void closeNotification(QString notificationId); public Q_SLOTS: void refreshContent(); void init(); void newTrackFile(const MusicAudioTrack &partialTrack); void resetImportedTracksCounter(); protected Q_SLOTS: void directoryChanged(const QString &path); void fileChanged(const QString &modifiedFileName); protected: virtual void executeInit(); virtual void triggerRefreshOfContent(); void scanDirectory(QList &newFiles, const QUrl &path); virtual MusicAudioTrack scanOneFile(const QUrl &scanFile); void watchPath(const QString &pathName); void addFileInDirectory(const QUrl &newFile, const QUrl &directoryName); void scanDirectoryTree(const QString &path); void setHandleNewFiles(bool handleThem); void emitNewFiles(const QList &tracks); void addCover(const MusicAudioTrack &newTrack); void removeDirectory(const QUrl &removedDirectory, QList &allRemovedFiles); void removeFile(const QUrl &oneRemovedTrack, QList &allRemovedFiles); void setSourceName(const QString &name); void increaseImportedTracksCount(); private: std::unique_ptr d; }; #endif // ABSTRACTFILELISTING_H diff --git a/src/allalbumsmodel.cpp b/src/allalbumsmodel.cpp index 7f30d2f9..ac988340 100644 --- a/src/allalbumsmodel.cpp +++ b/src/allalbumsmodel.cpp @@ -1,310 +1,310 @@ /* * Copyright 2015-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "allalbumsmodel.h" #include "musicstatistics.h" #include "databaseinterface.h" #include "allartistsmodel.h" #include "albummodel.h" #include #include #include #include #include class AllAlbumsModelPrivate { public: AllAlbumsModelPrivate() { } - QVector mAllAlbums; + QVector mAllAlbums; - int mAlbumCount = 0; + QHash mAlbumsData; AllArtistsModel *mAllArtistsModel = nullptr; }; AllAlbumsModel::AllAlbumsModel(QObject *parent) : QAbstractItemModel(parent), d(std::make_unique()) { } AllAlbumsModel::~AllAlbumsModel() = default; int AllAlbumsModel::albumCount() const { - return d->mAlbumCount; + return d->mAllAlbums.size(); } int AllAlbumsModel::rowCount(const QModelIndex &parent) const { auto albumCount = 0; if (parent.isValid()) { return albumCount; } - albumCount = d->mAlbumCount; + albumCount = d->mAllAlbums.size(); return albumCount; } QHash AllAlbumsModel::roleNames() const { auto roles = QAbstractItemModel::roleNames(); roles[static_cast(ColumnsRoles::TitleRole)] = "title"; roles[static_cast(ColumnsRoles::AllTracksTitleRole)] = "tracksTitle"; roles[static_cast(ColumnsRoles::ArtistRole)] = "artist"; roles[static_cast(ColumnsRoles::AllArtistsRole)] = "allArtists"; roles[static_cast(ColumnsRoles::ImageRole)] = "image"; roles[static_cast(ColumnsRoles::CountRole)] = "count"; roles[static_cast(ColumnsRoles::IsSingleDiscAlbumRole)] = "isSingleDiscAlbum"; roles[static_cast(ColumnsRoles::ContainerDataRole)] = "containerData"; roles[static_cast(ColumnsRoles::HighestTrackRating)] = "highestTrackRating"; roles[static_cast(ColumnsRoles::AlbumDatabaseIdRole)] = "databaseId"; roles[static_cast(ColumnsRoles::SecondaryTextRole)] = "secondaryText"; roles[static_cast(ColumnsRoles::ImageUrlRole)] = "imageUrl"; roles[static_cast(ColumnsRoles::ShadowForImageRole)] = "shadowForImage"; roles[static_cast(ColumnsRoles::ChildModelRole)] = "childModel"; roles[static_cast(ColumnsRoles::IsTracksContainerRole)] = "isTracksContainer"; return roles; } Qt::ItemFlags AllAlbumsModel::flags(const QModelIndex &index) const { if (!index.isValid()) { return Qt::NoItemFlags; } return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } QVariant AllAlbumsModel::data(const QModelIndex &index, int role) const { auto result = QVariant(); - const auto albumCount = d->mAlbumCount; + const auto albumCount = d->mAllAlbums.size(); if (!index.isValid()) { return result; } if (index.column() != 0) { return result; } if (index.row() < 0) { return result; } if (index.parent().isValid()) { return result; } if (index.internalId() != 0) { return result; } if (index.row() < 0 || index.row() >= albumCount) { return result; } result = internalDataAlbum(index.row(), role); return result; } QVariant AllAlbumsModel::internalDataAlbum(int albumIndex, int role) const { auto result = QVariant(); switch(role) { case ColumnsRoles::TitleRole: - result = d->mAllAlbums[albumIndex].title(); + result = d->mAlbumsData[d->mAllAlbums[albumIndex]].title(); break; case ColumnsRoles::AllTracksTitleRole: - result = d->mAllAlbums[albumIndex].allTracksTitle(); + result = d->mAlbumsData[d->mAllAlbums[albumIndex]].allTracksTitle(); break; case ColumnsRoles::ArtistRole: - result = d->mAllAlbums[albumIndex].artist(); + result = d->mAlbumsData[d->mAllAlbums[albumIndex]].artist(); break; case ColumnsRoles::AllArtistsRole: - result = d->mAllAlbums[albumIndex].allArtists().join(QStringLiteral(", ")); + result = d->mAlbumsData[d->mAllAlbums[albumIndex]].allArtists().join(QStringLiteral(", ")); break; case ColumnsRoles::ImageRole: { - auto albumArt = d->mAllAlbums[albumIndex].albumArtURI(); + auto albumArt = d->mAlbumsData[d->mAllAlbums[albumIndex]].albumArtURI(); if (albumArt.isValid()) { result = albumArt; } break; } case ColumnsRoles::CountRole: - result = d->mAllAlbums[albumIndex].tracksCount(); + result = d->mAlbumsData[d->mAllAlbums[albumIndex]].tracksCount(); break; case ColumnsRoles::IdRole: - result = d->mAllAlbums[albumIndex].id(); + result = d->mAlbumsData[d->mAllAlbums[albumIndex]].id(); break; case ColumnsRoles::IsSingleDiscAlbumRole: - result = d->mAllAlbums[albumIndex].isSingleDiscAlbum(); + result = d->mAlbumsData[d->mAllAlbums[albumIndex]].isSingleDiscAlbum(); break; case ColumnsRoles::ContainerDataRole: - result = QVariant::fromValue(d->mAllAlbums[albumIndex]); + result = QVariant::fromValue(d->mAlbumsData[d->mAllAlbums[albumIndex]]); break; case ColumnsRoles::AlbumDatabaseIdRole: - result = QVariant::fromValue(d->mAllAlbums[albumIndex].databaseId()); + result = QVariant::fromValue(d->mAlbumsData[d->mAllAlbums[albumIndex]].databaseId()); break; case ColumnsRoles::HighestTrackRating: - result = d->mAllAlbums[albumIndex].highestTrackRating(); + result = d->mAlbumsData[d->mAllAlbums[albumIndex]].highestTrackRating(); break; case Qt::DisplayRole: - result = d->mAllAlbums[albumIndex].title(); + result = d->mAlbumsData[d->mAllAlbums[albumIndex]].title(); break; case ColumnsRoles::SecondaryTextRole: - result = d->mAllAlbums[albumIndex].artist(); + result = d->mAlbumsData[d->mAllAlbums[albumIndex]].artist(); break; case ColumnsRoles::ImageUrlRole: { - auto albumArt = d->mAllAlbums[albumIndex].albumArtURI(); + auto albumArt = d->mAlbumsData[d->mAllAlbums[albumIndex]].albumArtURI(); if (albumArt.isValid()) { result = albumArt; } else { result = QUrl(QStringLiteral("image://icon/media-optical-audio")); } break; } case ColumnsRoles::ShadowForImageRole: - result = d->mAllAlbums[albumIndex].albumArtURI().isValid(); + result = d->mAlbumsData[d->mAllAlbums[albumIndex]].albumArtURI().isValid(); break; case ColumnsRoles::ChildModelRole: { auto newModel = new AlbumModel(); - newModel->setAlbumData(d->mAllAlbums[albumIndex]); + newModel->setAlbumData(d->mAlbumsData[d->mAllAlbums[albumIndex]]); result = QVariant::fromValue(newModel); break; } case ColumnsRoles::IsTracksContainerRole: result = true; break; } return result; } QModelIndex AllAlbumsModel::index(int row, int column, const QModelIndex &parent) const { auto result = QModelIndex(); if (column != 0) { return result; } if (parent.isValid()) { return result; } result = createIndex(row, column); return result; } QModelIndex AllAlbumsModel::parent(const QModelIndex &child) const { Q_UNUSED(child) auto result = QModelIndex(); return result; } int AllAlbumsModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 1; } AllArtistsModel *AllAlbumsModel::allArtists() const { return d->mAllArtistsModel; } void AllAlbumsModel::albumAdded(const MusicAlbum &newAlbum) { if (newAlbum.isValid()) { beginInsertRows({}, d->mAllAlbums.size(), d->mAllAlbums.size()); - d->mAllAlbums.push_back(newAlbum); - ++d->mAlbumCount; + d->mAllAlbums.push_back(newAlbum.databaseId()); + d->mAlbumsData[newAlbum.databaseId()] = newAlbum; endInsertRows(); Q_EMIT albumCountChanged(); } } void AllAlbumsModel::albumRemoved(const MusicAlbum &removedAlbum) { - auto removedAlbumIterator = std::find(d->mAllAlbums.begin(), d->mAllAlbums.end(), removedAlbum); + auto removedAlbumIterator = std::find(d->mAllAlbums.begin(), d->mAllAlbums.end(), removedAlbum.databaseId()); if (removedAlbumIterator == d->mAllAlbums.end()) { return; } int albumIndex = removedAlbumIterator - d->mAllAlbums.begin(); beginRemoveRows({}, albumIndex, albumIndex); + d->mAlbumsData.remove(removedAlbum.databaseId()); d->mAllAlbums.erase(removedAlbumIterator); - --d->mAlbumCount; endRemoveRows(); Q_EMIT albumCountChanged(); } void AllAlbumsModel::albumModified(const MusicAlbum &modifiedAlbum) { - auto modifiedAlbumIterator = std::find(d->mAllAlbums.begin(), d->mAllAlbums.end(), modifiedAlbum); + auto modifiedAlbumIterator = std::find(d->mAllAlbums.begin(), d->mAllAlbums.end(), modifiedAlbum.databaseId()); if (modifiedAlbumIterator == d->mAllAlbums.end()) { return; } int albumIndex = modifiedAlbumIterator - d->mAllAlbums.begin(); - d->mAllAlbums[albumIndex] = modifiedAlbum; + d->mAlbumsData[modifiedAlbum.databaseId()] = modifiedAlbum; Q_EMIT dataChanged(index(albumIndex, 0), index(albumIndex, 0)); } void AllAlbumsModel::setAllArtists(AllArtistsModel *model) { if (d->mAllArtistsModel == model) { return; } d->mAllArtistsModel = model; Q_EMIT allArtistsChanged(); } #include "moc_allalbumsmodel.cpp" diff --git a/src/allartistsmodel.cpp b/src/allartistsmodel.cpp index d6127546..0b96b18a 100644 --- a/src/allartistsmodel.cpp +++ b/src/allartistsmodel.cpp @@ -1,262 +1,256 @@ /* * Copyright 2015-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "allartistsmodel.h" #include "databaseinterface.h" #include "musicartist.h" #include "allalbumsmodel.h" #include #include #include #include #include class AllArtistsModelPrivate { public: AllArtistsModelPrivate() { } QVector mAllArtists; - int mArtistsCount = 0; - - bool mUseLocalIcons = false; - AllAlbumsModel *mAllAlbumsModel = nullptr; }; AllArtistsModel::AllArtistsModel(QObject *parent) : QAbstractItemModel(parent), d(std::make_unique()) { } AllArtistsModel::~AllArtistsModel() = default; int AllArtistsModel::rowCount(const QModelIndex &parent) const { auto artistCount = 0; if (parent.isValid()) { return artistCount; } - artistCount = d->mArtistsCount; + artistCount = d->mAllArtists.size(); return artistCount; } QHash AllArtistsModel::roleNames() const { auto roles = QAbstractItemModel::roleNames(); roles[static_cast(ColumnsRoles::NameRole)] = "name"; roles[static_cast(ColumnsRoles::ArtistsCountRole)] = "albumsCount"; roles[static_cast(ColumnsRoles::ImageRole)] = "image"; roles[static_cast(ColumnsRoles::IdRole)] = "databaseId"; roles[static_cast(ColumnsRoles::SecondaryTextRole)] = "secondaryText"; roles[static_cast(ColumnsRoles::ImageUrlRole)] = "imageUrl"; roles[static_cast(ColumnsRoles::ShadowForImageRole)] = "shadowForImage"; roles[static_cast(ColumnsRoles::ContainerDataRole)] = "containerData"; roles[static_cast(ColumnsRoles::ChildModelRole)] = "childModel"; roles[static_cast(ColumnsRoles::IsTracksContainerRole)] = "isTracksContainer"; return roles; } Qt::ItemFlags AllArtistsModel::flags(const QModelIndex &index) const { if (!index.isValid()) { return Qt::NoItemFlags; } return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } QVariant AllArtistsModel::data(const QModelIndex &index, int role) const { auto result = QVariant(); - const auto artistsCount = d->mArtistsCount; + const auto artistsCount = d->mAllArtists.size(); if (!index.isValid()) { return result; } if (index.column() != 0) { return result; } if (index.row() < 0) { return result; } if (index.parent().isValid()) { return result; } if (index.internalId() != 0) { return result; } if (index.row() < 0 || index.row() >= artistsCount) { return result; } switch(role) { case ColumnsRoles::NameRole: result = d->mAllArtists[index.row()].name(); break; case ColumnsRoles::ArtistsCountRole: result = d->mAllArtists[index.row()].albumsCount(); break; case ColumnsRoles::ImageRole: break; case ColumnsRoles::IdRole: break; case ColumnsRoles::SecondaryTextRole: result = QString(); break; case Qt::DisplayRole: result = d->mAllArtists[index.row()].name(); break; case ColumnsRoles::ImageUrlRole: result = QUrl(QStringLiteral("image://icon/view-media-artist")); break; case ColumnsRoles::ShadowForImageRole: result = false; break; case ColumnsRoles::ContainerDataRole: result = QVariant::fromValue(d->mAllArtists[index.row()]); break; case ColumnsRoles::ChildModelRole: { auto newModel = new QSortFilterProxyModel; newModel->setSourceModel(d->mAllAlbumsModel); newModel->setFilterRole(AllAlbumsModel::AllArtistsRole); newModel->setFilterRegExp(QRegExp(QStringLiteral(".*") + d->mAllArtists[index.row()].name() + QStringLiteral(".*"), Qt::CaseInsensitive)); result = QVariant::fromValue(newModel); break; } case ColumnsRoles::IsTracksContainerRole: result = false; break; } return result; } QModelIndex AllArtistsModel::index(int row, int column, const QModelIndex &parent) const { auto result = QModelIndex(); if (column != 0) { return result; } if (parent.isValid()) { return result; } result = createIndex(row, column); return result; } QModelIndex AllArtistsModel::parent(const QModelIndex &child) const { Q_UNUSED(child) auto result = QModelIndex(); return result; } int AllArtistsModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 1; } AllAlbumsModel *AllArtistsModel::allAlbums() const { return d->mAllAlbumsModel; } QAbstractItemModel *AllArtistsModel::itemModelForName(const QString &name) const { auto newModel = new QSortFilterProxyModel; newModel->setSourceModel(d->mAllAlbumsModel); newModel->setFilterRole(AllAlbumsModel::AllArtistsRole); newModel->setFilterRegExp(QRegExp(QStringLiteral(".*") + name + QStringLiteral(".*"), Qt::CaseInsensitive)); return newModel; } void AllArtistsModel::artistAdded(const MusicArtist &newArtist) { if (newArtist.isValid()) { beginInsertRows({}, d->mAllArtists.size(), d->mAllArtists.size()); d->mAllArtists.push_back(newArtist); - ++d->mArtistsCount; endInsertRows(); } } void AllArtistsModel::artistRemoved(const MusicArtist &removedArtist) { auto removedArtistIterator = std::find(d->mAllArtists.begin(), d->mAllArtists.end(), removedArtist); if (removedArtistIterator == d->mAllArtists.end()) { return; } int artistIndex = removedArtistIterator - d->mAllArtists.begin(); beginRemoveRows({}, artistIndex, artistIndex); d->mAllArtists.erase(removedArtistIterator); - --d->mArtistsCount; endRemoveRows(); } void AllArtistsModel::artistModified(const MusicArtist &modifiedArtist) { Q_UNUSED(modifiedArtist); } void AllArtistsModel::setAllAlbums(AllAlbumsModel *model) { if (d->mAllAlbumsModel == model) { return; } d->mAllAlbumsModel = model; Q_EMIT allAlbumsChanged(); } #include "moc_allartistsmodel.cpp" diff --git a/src/alltracksmodel.cpp b/src/alltracksmodel.cpp index 062a8922..a6a66c69 100644 --- a/src/alltracksmodel.cpp +++ b/src/alltracksmodel.cpp @@ -1,347 +1,352 @@ /* * Copyright 2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "alltracksmodel.h" #include #include class AllTracksModelPrivate { public: QHash mAllTracks; QList mIds; }; AllTracksModel::AllTracksModel(QObject *parent) : QAbstractItemModel(parent), d(std::make_unique()) { } AllTracksModel::~AllTracksModel() = default; int AllTracksModel::rowCount(const QModelIndex &parent) const { auto tracksCount = 0; if (parent.isValid()) { return tracksCount; } tracksCount = d->mAllTracks.size(); return tracksCount; } QHash AllTracksModel::roleNames() const { auto roles = QAbstractItemModel::roleNames(); roles[static_cast(ColumnsRoles::TitleRole)] = "title"; roles[static_cast(ColumnsRoles::DurationRole)] = "duration"; roles[static_cast(ColumnsRoles::ArtistRole)] = "artist"; roles[static_cast(ColumnsRoles::AlbumRole)] = "album"; roles[static_cast(ColumnsRoles::AlbumArtistRole)] = "albumArtist"; roles[static_cast(ColumnsRoles::TrackNumberRole)] = "trackNumber"; roles[static_cast(ColumnsRoles::DiscNumberRole)] = "discNumber"; roles[static_cast(ColumnsRoles::RatingRole)] = "rating"; roles[static_cast(ColumnsRoles::GenreRole)] = "genre"; roles[static_cast(ColumnsRoles::LyricistRole)] = "lyricist"; roles[static_cast(ColumnsRoles::ComposerRole)] = "composer"; roles[static_cast(ColumnsRoles::CommentRole)] = "comment"; roles[static_cast(ColumnsRoles::YearRole)] = "year"; roles[static_cast(ColumnsRoles::ChannelsRole)] = "channels"; roles[static_cast(ColumnsRoles::BitRateRole)] = "bitRate"; roles[static_cast(ColumnsRoles::SampleRateRole)] = "sampleRate"; roles[static_cast(ColumnsRoles::ImageRole)] = "image"; roles[static_cast(ColumnsRoles::DatabaseIdRole)] = "databaseId"; roles[static_cast(ColumnsRoles::IsSingleDiscAlbumRole)] = "isSingleDiscAlbum"; roles[static_cast(ColumnsRoles::ContainerDataRole)] = "containerData"; roles[static_cast(ColumnsRoles::ResourceRole)] = "trackResource"; roles[static_cast(ColumnsRoles::SecondaryTextRole)] = "secondaryText"; roles[static_cast(ColumnsRoles::ImageUrlRole)] = "imageUrl"; roles[static_cast(ColumnsRoles::ShadowForImageRole)] = "shadowForImage"; return roles; } Qt::ItemFlags AllTracksModel::flags(const QModelIndex &index) const { if (!index.isValid()) { return Qt::NoItemFlags; } return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } QVariant AllTracksModel::data(const QModelIndex &index, int role) const { auto result = QVariant(); const auto tracksCount = d->mAllTracks.size(); if (!index.isValid()) { return result; } if (index.column() != 0) { return result; } if (index.row() < 0) { return result; } if (index.parent().isValid()) { return result; } if (index.internalId() != 0) { return result; } if (index.row() < 0 || index.row() >= tracksCount) { return result; } const auto &track = d->mAllTracks[d->mIds[index.row()]]; switch(role) { case ColumnsRoles::TitleRole: if (d->mAllTracks[d->mIds[index.row()]].title().isEmpty()) { result = {}; } result = d->mAllTracks[d->mIds[index.row()]].title(); break; case ColumnsRoles::MilliSecondsDurationRole: result = d->mAllTracks[d->mIds[index.row()]].duration().msecsSinceStartOfDay(); break; case ColumnsRoles::DurationRole: { QTime trackDuration = d->mAllTracks[d->mIds[index.row()]].duration(); if (trackDuration.hour() == 0) { result = trackDuration.toString(QStringLiteral("mm:ss")); } else { result = trackDuration.toString(); } break; } case ColumnsRoles::ArtistRole: result = d->mAllTracks[d->mIds[index.row()]].artist(); break; case ColumnsRoles::AlbumRole: result = d->mAllTracks[d->mIds[index.row()]].albumName(); break; case ColumnsRoles::AlbumArtistRole: result = d->mAllTracks[d->mIds[index.row()]].albumArtist(); break; case ColumnsRoles::TrackNumberRole: result = d->mAllTracks[d->mIds[index.row()]].trackNumber(); break; case ColumnsRoles::DiscNumberRole: result = d->mAllTracks[d->mIds[index.row()]].discNumber(); break; case ColumnsRoles::IsSingleDiscAlbumRole: result = d->mAllTracks[d->mIds[index.row()]].isSingleDiscAlbum(); break; case ColumnsRoles::RatingRole: result = d->mAllTracks[d->mIds[index.row()]].rating(); break; case ColumnsRoles::GenreRole: result = d->mAllTracks[d->mIds[index.row()]].genre(); break; case ColumnsRoles::LyricistRole: result = d->mAllTracks[d->mIds[index.row()]].lyricist(); break; case ColumnsRoles::ComposerRole: result = d->mAllTracks[d->mIds[index.row()]].composer(); break; case ColumnsRoles::CommentRole: result = d->mAllTracks[d->mIds[index.row()]].comment(); break; case ColumnsRoles::YearRole: result = d->mAllTracks[d->mIds[index.row()]].year(); break; case ColumnsRoles::ChannelsRole: result = d->mAllTracks[d->mIds[index.row()]].channels(); break; case ColumnsRoles::BitRateRole: result = d->mAllTracks[d->mIds[index.row()]].bitRate(); break; case ColumnsRoles::SampleRateRole: result = d->mAllTracks[d->mIds[index.row()]].sampleRate(); break; case ColumnsRoles::ImageRole: { const auto &imageUrl = d->mAllTracks[d->mIds[index.row()]].albumCover(); if (imageUrl.isValid()) { result = imageUrl; } break; } case ColumnsRoles::ResourceRole: result = d->mAllTracks[d->mIds[index.row()]].resourceURI(); break; case ColumnsRoles::IdRole: result = d->mAllTracks[d->mIds[index.row()]].title(); break; case ColumnsRoles::DatabaseIdRole: result = d->mAllTracks[d->mIds[index.row()]].databaseId(); break; case ColumnsRoles::ContainerDataRole: result = QVariant::fromValue(d->mAllTracks[d->mIds[index.row()]]); break; case Qt::DisplayRole: result = track.title(); break; case ColumnsRoles::SecondaryTextRole: { auto secondaryText = QString(); secondaryText = QStringLiteral("%1 - %2%3"); secondaryText = secondaryText.arg(track.trackNumber()); secondaryText = secondaryText.arg(track.title()); if (track.artist() == track.albumArtist()) { secondaryText = secondaryText.arg(QString()); } else { auto artistText = QString(); artistText = QStringLiteral(" - %1"); artistText = artistText.arg(track.artist()); secondaryText = secondaryText.arg(artistText); } result = secondaryText; break; } case ColumnsRoles::ImageUrlRole: { const auto &imageUrl = d->mAllTracks[d->mIds[index.row()]].albumCover(); if (imageUrl.isValid()) { result = imageUrl; } else { result = QUrl(QStringLiteral("image://icon/media-optical-audio")); } break; } case ColumnsRoles::ShadowForImageRole: result = d->mAllTracks[d->mIds[index.row()]].albumCover().isValid(); break; } return result; } QModelIndex AllTracksModel::index(int row, int column, const QModelIndex &parent) const { auto result = QModelIndex(); if (column != 0) { return result; } if (parent.isValid()) { return result; } if (row > d->mAllTracks.size() - 1) { return result; } result = createIndex(row, column); return result; } QModelIndex AllTracksModel::parent(const QModelIndex &child) const { Q_UNUSED(child) auto result = QModelIndex(); return result; } int AllTracksModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 1; } void AllTracksModel::tracksAdded(const QList &allTracks) { auto newAllTracks = d->mAllTracks; auto newTracksIds = QList(); int countNewTracks = 0; for (const auto &oneTrack : allTracks) { if (newAllTracks.find(oneTrack.databaseId()) == newAllTracks.end()) { newAllTracks[oneTrack.databaseId()] = oneTrack; newTracksIds.push_back(oneTrack.databaseId()); ++countNewTracks; } } if (countNewTracks > 0) { beginInsertRows({}, d->mAllTracks.size(), d->mAllTracks.size() + countNewTracks - 1); d->mAllTracks = newAllTracks; d->mIds.append(newTracksIds); endInsertRows(); } } void AllTracksModel::trackRemoved(qulonglong removedTrackId) { auto itTrack = std::find(d->mIds.begin(), d->mIds.end(), removedTrackId); if (itTrack == d->mIds.end()) { return; } auto position = itTrack - d->mIds.begin(); beginRemoveRows({}, position, position); d->mIds.erase(itTrack); d->mAllTracks.remove(removedTrackId); endRemoveRows(); } void AllTracksModel::trackModified(const MusicAudioTrack &modifiedTrack) { + auto trackExists = (d->mAllTracks.find(modifiedTrack.databaseId()) != d->mAllTracks.end()); + if (!trackExists) { + return; + } + auto itTrack = std::find(d->mIds.begin(), d->mIds.end(), modifiedTrack.databaseId()); if (itTrack == d->mIds.end()) { return; } auto position = itTrack - d->mIds.begin(); d->mAllTracks[modifiedTrack.databaseId()] = modifiedTrack; Q_EMIT dataChanged(index(position, 0), index(position, 0)); } #include "moc_alltracksmodel.cpp" diff --git a/src/baloo/localbaloofilelisting.cpp b/src/baloo/localbaloofilelisting.cpp index 7d191c4a..1daa5c4b 100644 --- a/src/baloo/localbaloofilelisting.cpp +++ b/src/baloo/localbaloofilelisting.cpp @@ -1,506 +1,506 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "localbaloofilelisting.h" #include "musicaudiotrack.h" #include "notificationitem.h" #include "elisa_settings.h" #include "baloo/scheduler.h" #include "baloo/fileindexer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class LocalBalooFileListingPrivate { public: Baloo::Query mQuery; QHash> mAllAlbums; QHash> mNewAlbums; QList mNewTracks; QHash mAllAlbumCover; QAtomicInt mStopRequest = 0; QDBusServiceWatcher mServiceWatcher; bool mIsRegistered = false; bool mIsRegistering = false; QScopedPointer mBalooIndexer; QScopedPointer mBalooScheduler; }; LocalBalooFileListing::LocalBalooFileListing(QObject *parent) : AbstractFileListing(QStringLiteral("baloo"), parent), d(std::make_unique()) { d->mQuery.addType(QStringLiteral("Audio")); setHandleNewFiles(false); auto sessionBus = QDBusConnection::sessionBus(); connect(&d->mServiceWatcher, &QDBusServiceWatcher::serviceRegistered, this, &LocalBalooFileListing::serviceRegistered); connect(&d->mServiceWatcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &LocalBalooFileListing::serviceOwnerChanged); connect(&d->mServiceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &LocalBalooFileListing::serviceUnregistered); d->mServiceWatcher.setConnection(sessionBus); d->mServiceWatcher.addWatchedService(QStringLiteral("org.kde.baloo")); if (sessionBus.interface()->isServiceRegistered(QStringLiteral("org.kde.baloo"))) { registerToBaloo(); } } LocalBalooFileListing::~LocalBalooFileListing() { Q_EMIT closeNotification(QStringLiteral("balooInvalidConfiguration")); } void LocalBalooFileListing::applicationAboutToQuit() { AbstractFileListing::applicationAboutToQuit(); d->mStopRequest = 1; } void LocalBalooFileListing::newBalooFile(const QString &fileName) { auto newFile = QUrl::fromLocalFile(fileName); auto newTrack = scanOneFile(newFile); if (newTrack.isValid()) { QFileInfo newFileInfo(fileName); addFileInDirectory(newFile, QUrl::fromLocalFile(newFileInfo.absoluteDir().absolutePath())); emitNewFiles({newTrack}); } } void LocalBalooFileListing::registeredToBaloo(QDBusPendingCallWatcher *watcher) { qDebug() << "LocalBalooFileListing::registeredToBaloo"; if (!watcher) { return; } QDBusPendingReply<> reply = *watcher; if (reply.isError()) { qDebug() << "LocalBalooFileListing::executeInit" << reply.error().name() << reply.error().message(); d->mIsRegistered = false; } else { d->mIsRegistered = true; } d->mIsRegistering = false; watcher->deleteLater(); } void LocalBalooFileListing::registerToBaloo() { if (d->mIsRegistering) { qDebug() << "LocalBalooFileListing::registerToBaloo" << "already registering"; return; } qDebug() << "LocalBalooFileListing::registerToBaloo"; d->mIsRegistering = true; auto sessionBus = QDBusConnection::sessionBus(); d->mBalooIndexer.reset(new org::kde::baloo::fileindexer(QStringLiteral("org.kde.baloo"), QStringLiteral("/fileindexer"), sessionBus, this)); if (!d->mBalooIndexer->isValid()) { qDebug() << "LocalBalooFileListing::registerToBaloo" << "invalid org.kde.baloo/fileindexer interface"; return; } connect(d->mBalooIndexer.data(), &org::kde::baloo::fileindexer::finishedIndexingFile, this, &LocalBalooFileListing::newBalooFile); d->mBalooScheduler.reset(new org::kde::baloo::scheduler(QStringLiteral("org.kde.baloo"), QStringLiteral("/scheduler"), sessionBus, this)); if (!d->mBalooScheduler->isValid()) { qDebug() << "LocalBalooFileListing::registerToBaloo" << "invalid org.kde.baloo/scheduler interface"; return; } qDebug() << "LocalBalooFileListing::registerToBaloo" << "call registerMonitor"; auto answer = d->mBalooIndexer->registerMonitor(); if (answer.isError()) { qDebug() << "LocalBalooFileListing::executeInit" << answer.error().name() << answer.error().message(); } auto pendingCallWatcher = new QDBusPendingCallWatcher(answer); connect(pendingCallWatcher, &QDBusPendingCallWatcher::finished, this, &LocalBalooFileListing::registeredToBaloo); if (pendingCallWatcher->isFinished()) { registeredToBaloo(pendingCallWatcher); } } void LocalBalooFileListing::renamedFiles(const QString &from, const QString &to, const QStringList &listFiles) { qDebug() << "LocalBalooFileListing::renamedFiles" << from << to << listFiles; } void LocalBalooFileListing::serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner) { Q_UNUSED(oldOwner); Q_UNUSED(newOwner); qDebug() << "LocalBalooFileListing::serviceOwnerChanged" << serviceName << oldOwner << newOwner; if (serviceName == QStringLiteral("org.kde.baloo") && !newOwner.isEmpty()) { d->mIsRegistered = false; registerToBaloo(); } } void LocalBalooFileListing::serviceRegistered(const QString &serviceName) { qDebug() << "LocalBalooFileListing::serviceRegistered" << serviceName; if (serviceName == QStringLiteral("org.kde.baloo")) { registerToBaloo(); } } void LocalBalooFileListing::serviceUnregistered(const QString &serviceName) { qDebug() << "LocalBalooFileListing::serviceUnregistered" << serviceName; if (serviceName == QStringLiteral("org.kde.baloo")) { d->mIsRegistered = false; } } void LocalBalooFileListing::executeInit() { } void LocalBalooFileListing::triggerRefreshOfContent() { if (!checkBalooConfiguration()) { return; } Q_EMIT indexingStarted(); AbstractFileListing::triggerRefreshOfContent(); auto resultIterator = d->mQuery.exec(); auto newFiles = QList(); while(resultIterator.next() && d->mStopRequest == 0) { const auto &newFileUrl = QUrl::fromLocalFile(resultIterator.filePath()); auto scanFileInfo = QFileInfo(resultIterator.filePath()); const auto currentDirectory = QUrl::fromLocalFile(scanFileInfo.absoluteDir().absolutePath()); addFileInDirectory(newFileUrl, currentDirectory); const auto &newTrack = scanOneFile(newFileUrl); if (newTrack.isValid()) { newFiles.push_back(newTrack); increaseImportedTracksCount(); if (newFiles.size() % 50 == 0) { Q_EMIT importedTracksCountChanged(); } if (newFiles.size() > 500 && d->mStopRequest == 0) { Q_EMIT importedTracksCountChanged(); emitNewFiles(newFiles); newFiles.clear(); } } } if (!newFiles.isEmpty() && d->mStopRequest == 0) { Q_EMIT importedTracksCountChanged(); emitNewFiles(newFiles); } - Q_EMIT indexingFinished(); + Q_EMIT indexingFinished(importedTracksCount()); } MusicAudioTrack LocalBalooFileListing::scanOneFile(const QUrl &scanFile) { auto newTrack = MusicAudioTrack(); auto fileName = scanFile.toLocalFile(); auto scanFileInfo = QFileInfo(fileName); if (scanFileInfo.exists()) { watchPath(fileName); } Baloo::File match(fileName); match.load(); const auto &allProperties = match.properties(); auto titleProperty = allProperties.find(KFileMetaData::Property::Title); auto durationProperty = allProperties.find(KFileMetaData::Property::Duration); auto artistProperty = allProperties.find(KFileMetaData::Property::Artist); auto albumProperty = allProperties.find(KFileMetaData::Property::Album); auto albumArtistProperty = allProperties.find(KFileMetaData::Property::AlbumArtist); auto trackNumberProperty = allProperties.find(KFileMetaData::Property::TrackNumber); auto discNumberProperty = allProperties.find(KFileMetaData::Property::DiscNumber); auto genreProperty = allProperties.find(KFileMetaData::Property::Genre); auto yearProperty = allProperties.find(KFileMetaData::Property::ReleaseYear); auto composerProperty = allProperties.find(KFileMetaData::Property::Composer); auto lyricistProperty = allProperties.find(KFileMetaData::Property::Lyricist); auto channelsProperty = allProperties.find(KFileMetaData::Property::Channels); auto bitRateProperty = allProperties.find(KFileMetaData::Property::BitRate); auto sampleRateProperty = allProperties.find(KFileMetaData::Property::SampleRate); auto commentProperty = allProperties.find(KFileMetaData::Property::Comment); auto fileData = KFileMetaData::UserMetaData(fileName); if (albumProperty != allProperties.end()) { auto albumValue = albumProperty->toString(); auto &allTracks = d->mAllAlbums[albumValue]; newTrack.setAlbumName(albumValue); if (artistProperty != allProperties.end()) { newTrack.setArtist(artistProperty->toString()); } if (durationProperty != allProperties.end()) { newTrack.setDuration(QTime::fromMSecsSinceStartOfDay(1000 * durationProperty->toDouble())); } if (titleProperty != allProperties.end()) { newTrack.setTitle(titleProperty->toString()); } if (trackNumberProperty != allProperties.end()) { newTrack.setTrackNumber(trackNumberProperty->toInt()); } if (discNumberProperty != allProperties.end()) { newTrack.setDiscNumber(discNumberProperty->toInt()); } else { newTrack.setDiscNumber(1); } if (albumArtistProperty != allProperties.end()) { if (albumArtistProperty->canConvert()) { newTrack.setAlbumArtist(albumArtistProperty->toString()); } else if (albumArtistProperty->canConvert()) { newTrack.setAlbumArtist(albumArtistProperty->toStringList().join(QStringLiteral(", "))); } } if (newTrack.artist().isEmpty()) { newTrack.setArtist(newTrack.albumArtist()); } if (yearProperty != allProperties.end()) { newTrack.setYear(yearProperty->toInt()); } if (channelsProperty != allProperties.end()) { newTrack.setChannels(channelsProperty->toInt()); } if (bitRateProperty != allProperties.end()) { newTrack.setBitRate(bitRateProperty->toInt()); } if (sampleRateProperty != allProperties.end()) { newTrack.setSampleRate(sampleRateProperty->toInt()); } if (genreProperty != allProperties.end()) { newTrack.setGenre(genreProperty->toString()); } if (composerProperty != allProperties.end()) { newTrack.setComposer(composerProperty->toString()); } if (lyricistProperty != allProperties.end()) { newTrack.setLyricist(lyricistProperty->toString()); } if (commentProperty != allProperties.end()) { newTrack.setComment(commentProperty->toString()); } newTrack.setRating(fileData.rating()); newTrack.setResourceURI(scanFile); QFileInfo coverFilePath(scanFileInfo.dir().filePath(QStringLiteral("cover.jpg"))); if (coverFilePath.exists()) { d->mAllAlbumCover[albumValue] = QUrl::fromLocalFile(coverFilePath.absoluteFilePath()); } else { coverFilePath.setFile(scanFileInfo.dir().filePath(QStringLiteral("cover.png"))); if (coverFilePath.exists()) { d->mAllAlbumCover[albumValue] = QUrl::fromLocalFile(coverFilePath.absoluteFilePath()); } } auto itTrack = std::find(allTracks.begin(), allTracks.end(), newTrack); if (itTrack == allTracks.end()) { allTracks.push_back(newTrack); d->mNewTracks.push_back(newTrack); d->mNewAlbums[newTrack.albumName()].push_back(newTrack); auto &newTracks = d->mAllAlbums[newTrack.albumName()]; std::sort(allTracks.begin(), allTracks.end()); std::sort(newTracks.begin(), newTracks.end()); } if (newTrack.title().isEmpty()) { return newTrack; } if (newTrack.artist().isEmpty()) { return newTrack; } if (newTrack.albumName().isEmpty()) { return newTrack; } if (!newTrack.duration().isValid()) { return newTrack; } newTrack.setValid(true); } if (!newTrack.isValid()) { newTrack = AbstractFileListing::scanOneFile(scanFile); } if (newTrack.isValid()) { addCover(newTrack); } return newTrack; } bool LocalBalooFileListing::checkBalooConfiguration() { bool problemDetected = false; Baloo::IndexerConfig balooConfiguration; problemDetected = problemDetected || !balooConfiguration.fileIndexingEnabled(); problemDetected = problemDetected || balooConfiguration.onlyBasicIndexing(); if (problemDetected) { NotificationItem balooInvalidConfiguration; balooInvalidConfiguration.setNotificationId(QStringLiteral("balooInvalidConfiguration")); balooInvalidConfiguration.setTargetObject(this); balooInvalidConfiguration.setMessage(i18nc("Notification about unusable Baloo Configuration", "Baloo configuration does not allow to discover your music")); balooInvalidConfiguration.setMainButtonText(i18nc("Text of button to modify Baloo Configuration", "Modify it")); balooInvalidConfiguration.setMainButtonIconName(QStringLiteral("configure")); balooInvalidConfiguration.setMainButtonMethodName(QStringLiteral("fixBalooConfiguration")); balooInvalidConfiguration.setSecondaryButtonText(i18nc("Text of button to disable Baloo indexer", "Disable Baloo support")); balooInvalidConfiguration.setSecondaryButtonIconName(QStringLiteral("configure")); balooInvalidConfiguration.setSecondaryButtonMethodName(QStringLiteral("disableBalooIndexer")); Q_EMIT newNotification(balooInvalidConfiguration); } else { Q_EMIT closeNotification(QStringLiteral("balooInvalidConfiguration")); } return !problemDetected; } void LocalBalooFileListing::fixBalooConfiguration() { qDebug() << "LocalBalooFileListing::fixBalooConfiguration"; Baloo::IndexerConfig balooConfiguration; if (!balooConfiguration.fileIndexingEnabled()) { balooConfiguration.setFileIndexingEnabled(true); } if (balooConfiguration.onlyBasicIndexing()) { balooConfiguration.setOnlyBasicIndexing(false); } balooConfiguration.refresh(); if (checkBalooConfiguration()) { triggerRefreshOfContent(); } } void LocalBalooFileListing::disableBalooIndexer() { Elisa::ElisaConfiguration::self()->setBalooIndexer(false); Elisa::ElisaConfiguration::self()->save(); } #include "moc_localbaloofilelisting.cpp" diff --git a/src/databaseinterface.cpp b/src/databaseinterface.cpp index 4f237a82..82e48255 100644 --- a/src/databaseinterface.cpp +++ b/src/databaseinterface.cpp @@ -1,3534 +1,3617 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "databaseinterface.h" #include #include #include #include #include #include #include #include #include #include #include class DatabaseInterfacePrivate { public: DatabaseInterfacePrivate(const QSqlDatabase &tracksDatabase) : mTracksDatabase(tracksDatabase), mSelectAlbumQuery(mTracksDatabase), mSelectTrackQuery(mTracksDatabase), mSelectAlbumIdFromTitleQuery(mTracksDatabase), mInsertAlbumQuery(mTracksDatabase), mSelectTrackIdFromTitleAlbumIdArtistQuery(mTracksDatabase), mInsertTrackQuery(mTracksDatabase), mSelectAlbumTrackCountQuery(mTracksDatabase), mUpdateAlbumQuery(mTracksDatabase), mSelectTracksFromArtist(mTracksDatabase), mSelectTrackFromIdQuery(mTracksDatabase), mSelectCountAlbumsForArtistQuery(mTracksDatabase), mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery(mTracksDatabase), mSelectAllAlbumsQuery(mTracksDatabase), mSelectAllAlbumsFromArtistQuery(mTracksDatabase), mSelectAllArtistsQuery(mTracksDatabase), mInsertArtistsQuery(mTracksDatabase), mSelectArtistByNameQuery(mTracksDatabase), mSelectArtistQuery(mTracksDatabase), mSelectTrackFromFilePathQuery(mTracksDatabase), mRemoveTrackQuery(mTracksDatabase), mRemoveAlbumQuery(mTracksDatabase), mRemoveArtistQuery(mTracksDatabase), mSelectAllTracksQuery(mTracksDatabase), mInsertTrackMapping(mTracksDatabase), mSelectAllTracksFromSourceQuery(mTracksDatabase), mInsertMusicSource(mTracksDatabase), mSelectMusicSource(mTracksDatabase), mUpdateIsSingleDiscAlbumFromIdQuery(mTracksDatabase), mSelectAllInvalidTracksFromSourceQuery(mTracksDatabase), mInitialUpdateTracksValidity(mTracksDatabase), mUpdateTrackMapping(mTracksDatabase), mSelectTracksMapping(mTracksDatabase), mSelectTracksMappingPriority(mTracksDatabase), mUpdateAlbumArtUriFromAlbumIdQuery(mTracksDatabase), mSelectTracksMappingPriorityByTrackId(mTracksDatabase), mSelectAllTrackFilesFromSourceQuery(mTracksDatabase), mFindInvalidTrackFilesQuery(mTracksDatabase), mSelectAlbumIdsFromArtist(mTracksDatabase), mRemoveTracksMappingFromSource(mTracksDatabase), mRemoveTracksMapping(mTracksDatabase), mSelectTracksWithoutMappingQuery(mTracksDatabase), mSelectAlbumIdFromTitleAndArtistQuery(mTracksDatabase), mSelectAlbumIdFromTitleWithoutArtistQuery(mTracksDatabase), mInsertAlbumArtistQuery(mTracksDatabase), mInsertTrackArtistQuery(mTracksDatabase), mRemoveTrackArtistQuery(mTracksDatabase), mRemoveAlbumArtistQuery(mTracksDatabase), - mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery(mTracksDatabase) + mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery(mTracksDatabase), mSelectAlbumArtUriFromAlbumIdQuery(mTracksDatabase) { } QSqlDatabase mTracksDatabase; QSqlQuery mSelectAlbumQuery; QSqlQuery mSelectTrackQuery; QSqlQuery mSelectAlbumIdFromTitleQuery; QSqlQuery mInsertAlbumQuery; QSqlQuery mSelectTrackIdFromTitleAlbumIdArtistQuery; QSqlQuery mInsertTrackQuery; QSqlQuery mSelectAlbumTrackCountQuery; QSqlQuery mUpdateAlbumQuery; QSqlQuery mSelectTracksFromArtist; QSqlQuery mSelectTrackFromIdQuery; QSqlQuery mSelectCountAlbumsForArtistQuery; QSqlQuery mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery; QSqlQuery mSelectAllAlbumsQuery; QSqlQuery mSelectAllAlbumsFromArtistQuery; QSqlQuery mSelectAllArtistsQuery; QSqlQuery mInsertArtistsQuery; QSqlQuery mSelectArtistByNameQuery; QSqlQuery mSelectArtistQuery; QSqlQuery mSelectTrackFromFilePathQuery; QSqlQuery mRemoveTrackQuery; QSqlQuery mRemoveAlbumQuery; QSqlQuery mRemoveArtistQuery; QSqlQuery mSelectAllTracksQuery; QSqlQuery mInsertTrackMapping; QSqlQuery mSelectAllTracksFromSourceQuery; QSqlQuery mInsertMusicSource; QSqlQuery mSelectMusicSource; QSqlQuery mUpdateIsSingleDiscAlbumFromIdQuery; QSqlQuery mSelectAllInvalidTracksFromSourceQuery; QSqlQuery mInitialUpdateTracksValidity; QSqlQuery mUpdateTrackMapping; QSqlQuery mSelectTracksMapping; QSqlQuery mSelectTracksMappingPriority; QSqlQuery mUpdateAlbumArtUriFromAlbumIdQuery; QSqlQuery mSelectTracksMappingPriorityByTrackId; QSqlQuery mSelectAllTrackFilesFromSourceQuery; QSqlQuery mFindInvalidTrackFilesQuery; QSqlQuery mSelectAlbumIdsFromArtist; QSqlQuery mRemoveTracksMappingFromSource; QSqlQuery mRemoveTracksMapping; QSqlQuery mSelectTracksWithoutMappingQuery; QSqlQuery mSelectAlbumIdFromTitleAndArtistQuery; QSqlQuery mSelectAlbumIdFromTitleWithoutArtistQuery; QSqlQuery mInsertAlbumArtistQuery; QSqlQuery mInsertTrackArtistQuery; QSqlQuery mRemoveTrackArtistQuery; QSqlQuery mRemoveAlbumArtistQuery; QSqlQuery mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery; + QSqlQuery mSelectAlbumArtUriFromAlbumIdQuery; + qulonglong mAlbumId = 1; qulonglong mArtistId = 1; qulonglong mTrackId = 1; qulonglong mDiscoverId = 1; bool mInitFinished = false; QAtomicInt mStopRequest = 0; }; DatabaseInterface::DatabaseInterface(QObject *parent) : QObject(parent), d(nullptr) { } DatabaseInterface::~DatabaseInterface() { if (d) { d->mTracksDatabase.close(); } } void DatabaseInterface::init(const QString &dbName, const QString &databaseFileName) { QSqlDatabase tracksDatabase = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), dbName); if (!databaseFileName.isEmpty()) { tracksDatabase.setDatabaseName(QStringLiteral("file:") + databaseFileName); } else { tracksDatabase.setDatabaseName(QStringLiteral("file:memdb1?mode=memory")); } tracksDatabase.setConnectOptions(QStringLiteral("foreign_keys = ON;locking_mode = EXCLUSIVE;QSQLITE_OPEN_URI;QSQLITE_BUSY_TIMEOUT=500000")); auto result = tracksDatabase.open(); if (result) { qDebug() << "database open"; } else { qDebug() << "database not open"; } qDebug() << "DatabaseInterface::init" << (tracksDatabase.driver()->hasFeature(QSqlDriver::Transactions) ? "yes" : "no"); d = std::make_unique(tracksDatabase); initDatabase(); initRequest(); if (!databaseFileName.isEmpty()) { reloadExistingDatabase(); } } MusicAlbum DatabaseInterface::albumFromTitleAndArtist(const QString &title, const QString &artist) { auto result = MusicAlbum(); auto transactionResult = startTransaction(); if (!transactionResult) { return result; } result = internalAlbumFromTitleAndArtist(title, artist); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } QList DatabaseInterface::allTracks() { auto result = QList(); if (!d) { return result; } auto transactionResult = startTransaction(); if (!transactionResult) { return result; } auto queryResult = d->mSelectAllTracksQuery.exec(); if (!queryResult || !d->mSelectAllTracksQuery.isSelect() || !d->mSelectAllTracksQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::allAlbums" << d->mSelectAllTracksQuery.lastQuery(); qDebug() << "DatabaseInterface::allAlbums" << d->mSelectAllTracksQuery.boundValues(); qDebug() << "DatabaseInterface::allAlbums" << d->mSelectAllTracksQuery.lastError(); return result; } while(d->mSelectAllTracksQuery.next()) { const auto ¤tRecord = d->mSelectAllTracksQuery.record(); result.push_back(buildTrackFromDatabaseRecord(currentRecord)); } d->mSelectAllTracksQuery.finish(); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } QList DatabaseInterface::allTracksFromSource(const QString &musicSource) { auto result = QList(); if (!d) { return result; } auto transactionResult = startTransaction(); if (!transactionResult) { return result; } d->mSelectAllTracksFromSourceQuery.bindValue(QStringLiteral(":source"), musicSource); auto queryResult = d->mSelectAllTracksFromSourceQuery.exec(); if (!queryResult || !d->mSelectAllTracksFromSourceQuery.isSelect() || !d->mSelectAllTracksFromSourceQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::allAlbums" << d->mSelectAllTracksFromSourceQuery.lastQuery(); qDebug() << "DatabaseInterface::allAlbums" << d->mSelectAllTracksFromSourceQuery.boundValues(); qDebug() << "DatabaseInterface::allAlbums" << d->mSelectAllTracksFromSourceQuery.lastError(); d->mSelectAllTracksFromSourceQuery.finish(); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } while(d->mSelectAllTracksFromSourceQuery.next()) { const auto ¤tRecord = d->mSelectAllTracksFromSourceQuery.record(); result.push_back(buildTrackFromDatabaseRecord(currentRecord)); } d->mSelectAllTracksFromSourceQuery.finish(); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } QList DatabaseInterface::allInvalidTracksFromSource(const QString &musicSource) { auto result = QList(); if (!d) { return result; } auto transactionResult = startTransaction(); if (!transactionResult) { return result; } d->mSelectAllInvalidTracksFromSourceQuery.bindValue(QStringLiteral(":source"), musicSource); auto queryResult = d->mSelectAllInvalidTracksFromSourceQuery.exec(); if (!queryResult || !d->mSelectAllInvalidTracksFromSourceQuery.isSelect() || !d->mSelectAllInvalidTracksFromSourceQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::allAlbums" << d->mSelectAllInvalidTracksFromSourceQuery.lastQuery(); qDebug() << "DatabaseInterface::allAlbums" << d->mSelectAllInvalidTracksFromSourceQuery.boundValues(); qDebug() << "DatabaseInterface::allAlbums" << d->mSelectAllInvalidTracksFromSourceQuery.lastError(); return result; } while(d->mSelectAllInvalidTracksFromSourceQuery.next()) { const auto ¤tRecord = d->mSelectAllInvalidTracksFromSourceQuery.record(); result.push_back(buildTrackFromDatabaseRecord(currentRecord)); } d->mSelectAllInvalidTracksFromSourceQuery.finish(); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } QList DatabaseInterface::allAlbums() { auto result = QList(); if (!d) { return result; } auto transactionResult = startTransaction(); if (!transactionResult) { return result; } auto queryResult = d->mSelectAllAlbumsQuery.exec(); if (!queryResult || !d->mSelectAllAlbumsQuery.isSelect() || !d->mSelectAllAlbumsQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::allAlbums" << d->mSelectAllAlbumsQuery.lastQuery(); qDebug() << "DatabaseInterface::allAlbums" << d->mSelectAllAlbumsQuery.boundValues(); qDebug() << "DatabaseInterface::allAlbums" << d->mSelectAllAlbumsQuery.lastError(); return result; } while(d->mSelectAllAlbumsQuery.next()) { auto newAlbum = MusicAlbum(); const auto ¤tRecord = d->mSelectAllAlbumsQuery.record(); newAlbum.setDatabaseId(currentRecord.value(0).toULongLong()); newAlbum.setTitle(currentRecord.value(1).toString()); newAlbum.setId(currentRecord.value(2).toString()); newAlbum.setArtist(currentRecord.value(3).toString()); newAlbum.setAlbumArtURI(currentRecord.value(4).toUrl()); newAlbum.setTracksCount(currentRecord.value(5).toInt()); newAlbum.setIsSingleDiscAlbum(currentRecord.value(6).toBool()); newAlbum.setTracks(fetchTracks(newAlbum.databaseId())); newAlbum.setValid(true); result.push_back(newAlbum); } d->mSelectAllAlbumsQuery.finish(); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } QList DatabaseInterface::allArtists() { auto result = QList(); if (!d) { return result; } auto transactionResult = startTransaction(); if (!transactionResult) { return result; } auto queryResult = d->mSelectAllArtistsQuery.exec(); if (!queryResult || !d->mSelectAllArtistsQuery.isSelect() || !d->mSelectAllArtistsQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::allArtists" << d->mSelectAllArtistsQuery.lastQuery(); qDebug() << "DatabaseInterface::allArtists" << d->mSelectAllArtistsQuery.boundValues(); qDebug() << "DatabaseInterface::allArtists" << d->mSelectAllArtistsQuery.lastError(); d->mSelectAllArtistsQuery.finish(); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } while(d->mSelectAllArtistsQuery.next()) { auto newArtist = MusicArtist(); const auto ¤tRecord = d->mSelectAllArtistsQuery.record(); newArtist.setDatabaseId(currentRecord.value(0).toULongLong()); newArtist.setName(currentRecord.value(1).toString()); newArtist.setValid(true); d->mSelectCountAlbumsForArtistQuery.bindValue(QStringLiteral(":artistName"), newArtist.name()); auto queryResult = d->mSelectCountAlbumsForArtistQuery.exec(); if (!queryResult || !d->mSelectCountAlbumsForArtistQuery.isSelect() || !d->mSelectCountAlbumsForArtistQuery.isActive() || !d->mSelectCountAlbumsForArtistQuery.next()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::allArtists" << d->mSelectCountAlbumsForArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::allArtists" << d->mSelectCountAlbumsForArtistQuery.boundValues(); qDebug() << "DatabaseInterface::allArtists" << d->mSelectCountAlbumsForArtistQuery.lastError(); d->mSelectCountAlbumsForArtistQuery.finish(); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } newArtist.setAlbumsCount(d->mSelectCountAlbumsForArtistQuery.record().value(0).toInt()); d->mSelectCountAlbumsForArtistQuery.finish(); result.push_back(newArtist); } d->mSelectAllArtistsQuery.finish(); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } QList DatabaseInterface::tracksFromAuthor(const QString &artistName) { auto allTracks = QList(); auto transactionResult = startTransaction(); if (!transactionResult) { return allTracks; } allTracks = internalTracksFromAuthor(artistName); transactionResult = finishTransaction(); if (!transactionResult) { return allTracks; } return allTracks; } MusicArtist DatabaseInterface::internalArtistFromId(qulonglong artistId) { auto result = MusicArtist(); if (!d || !d->mTracksDatabase.isValid() || !d->mInitFinished) { return result; } d->mSelectArtistQuery.bindValue(QStringLiteral(":artistId"), artistId); auto queryResult = d->mSelectArtistQuery.exec(); if (!queryResult || !d->mSelectArtistQuery.isSelect() || !d->mSelectArtistQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::internalArtistFromId" << d->mSelectArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::internalArtistFromId" << d->mSelectArtistQuery.boundValues(); qDebug() << "DatabaseInterface::internalArtistFromId" << d->mSelectArtistQuery.lastError(); d->mSelectArtistQuery.finish(); return result; } if (!d->mSelectArtistQuery.next()) { d->mSelectArtistQuery.finish(); return result; } const auto ¤tRecord = d->mSelectArtistQuery.record(); result.setDatabaseId(currentRecord.value(0).toULongLong()); result.setName(currentRecord.value(1).toString()); result.setValid(true); d->mSelectArtistQuery.finish(); d->mSelectCountAlbumsForArtistQuery.bindValue(QStringLiteral(":artistName"), result.name()); queryResult = d->mSelectCountAlbumsForArtistQuery.exec(); if (!queryResult || !d->mSelectCountAlbumsForArtistQuery.isSelect() || !d->mSelectCountAlbumsForArtistQuery.isActive() || !d->mSelectCountAlbumsForArtistQuery.next()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::internalArtistFromId" << d->mSelectCountAlbumsForArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::internalArtistFromId" << d->mSelectCountAlbumsForArtistQuery.boundValues(); qDebug() << "DatabaseInterface::internalArtistFromId" << d->mSelectCountAlbumsForArtistQuery.lastError(); d->mSelectCountAlbumsForArtistQuery.finish(); return result; } result.setAlbumsCount(d->mSelectCountAlbumsForArtistQuery.record().value(0).toInt()); d->mSelectCountAlbumsForArtistQuery.finish(); return result; } MusicAudioTrack DatabaseInterface::trackFromDatabaseId(qulonglong id) { auto result = MusicAudioTrack(); if (!d) { return result; } auto transactionResult = startTransaction(); if (!transactionResult) { return result; } result = internalTrackFromDatabaseId(id); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } qulonglong DatabaseInterface::trackIdFromTitleAlbumTrackDiscNumber(const QString &title, const QString &artist, const QString &album, int trackNumber, int discNumber) { auto result = qulonglong(0); if (!d) { return result; } auto transactionResult = startTransaction(); if (!transactionResult) { return result; } result = internalTrackIdFromTitleAlbumTracDiscNumber(title, artist, album, trackNumber, discNumber); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } qulonglong DatabaseInterface::trackIdFromFileName(const QUrl &fileName) { auto result = qulonglong(0); if (!d) { return result; } auto transactionResult = startTransaction(); if (!transactionResult) { return result; } result = internalTrackIdFromFileName(fileName); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } void DatabaseInterface::applicationAboutToQuit() { d->mStopRequest = 1; } void DatabaseInterface::removeAllTracksFromSource(const QString &sourceName) { auto transactionResult = startTransaction(); if (!transactionResult) { return; } d->mSelectMusicSource.bindValue(QStringLiteral(":name"), sourceName); auto queryResult = d->mSelectMusicSource.exec(); if (!queryResult || !d->mSelectMusicSource.isSelect() || !d->mSelectMusicSource.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mSelectMusicSource.lastQuery(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mSelectMusicSource.boundValues(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mSelectMusicSource.lastError(); d->mSelectMusicSource.finish(); transactionResult = finishTransaction(); if (!transactionResult) { return; } return; } if (!d->mSelectMusicSource.next()) { transactionResult = finishTransaction(); if (!transactionResult) { return; } return; } qulonglong sourceId = d->mSelectMusicSource.record().value(0).toULongLong(); d->mSelectMusicSource.finish(); d->mSelectAllTrackFilesFromSourceQuery.bindValue(QStringLiteral(":discoverId"), sourceId); queryResult = d->mSelectAllTrackFilesFromSourceQuery.exec(); if (!queryResult || !d->mSelectAllTrackFilesFromSourceQuery.isSelect() || !d->mSelectAllTrackFilesFromSourceQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mSelectAllTrackFilesFromSourceQuery.lastQuery(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mSelectAllTrackFilesFromSourceQuery.boundValues(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mSelectAllTrackFilesFromSourceQuery.lastError(); d->mSelectAllTrackFilesFromSourceQuery.finish(); transactionResult = finishTransaction(); if (!transactionResult) { return; } return; } QList allFileNames; while(d->mSelectAllTrackFilesFromSourceQuery.next()) { auto fileName = d->mSelectAllTrackFilesFromSourceQuery.record().value(0).toUrl(); allFileNames.push_back(fileName); } d->mSelectAllTrackFilesFromSourceQuery.finish(); internalRemoveTracksList(allFileNames, sourceId); transactionResult = finishTransaction(); if (!transactionResult) { return; } } void DatabaseInterface::cleanInvalidTracks() { if (d->mStopRequest == 1) { return; } auto transactionResult = startTransaction(); if (!transactionResult) { return; } auto queryResult = d->mFindInvalidTrackFilesQuery.exec(); if (!queryResult || !d->mFindInvalidTrackFilesQuery.isSelect() || !d->mFindInvalidTrackFilesQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mFindInvalidTrackFilesQuery.lastQuery(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mFindInvalidTrackFilesQuery.boundValues(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mFindInvalidTrackFilesQuery.lastError(); d->mFindInvalidTrackFilesQuery.finish(); transactionResult = finishTransaction(); if (!transactionResult) { return; } return; } QList allFileNames; auto sourceId = qulonglong(); while(d->mFindInvalidTrackFilesQuery.next()) { auto fileName = d->mFindInvalidTrackFilesQuery.record().value(0).toUrl(); sourceId = d->mFindInvalidTrackFilesQuery.record().value(1).toULongLong(); allFileNames.push_back(fileName); } d->mFindInvalidTrackFilesQuery.finish(); internalRemoveTracksList(allFileNames, sourceId); transactionResult = finishTransaction(); if (!transactionResult) { return; } } void DatabaseInterface::insertTracksList(const QList &tracks, const QHash &covers, const QString &musicSource) { if (d->mStopRequest == 1) { return; } auto transactionResult = startTransaction(); if (!transactionResult) { return; } QSet modifiedAlbumIds; QList insertedTracks; for(const auto &oneTrack : tracks) { d->mSelectTracksMapping.bindValue(QStringLiteral(":fileName"), oneTrack.resourceURI()); auto result = d->mSelectTracksMapping.exec(); if (!result || !d->mSelectTracksMapping.isSelect() || !d->mSelectTracksMapping.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertTracksList" << d->mSelectTracksMapping.lastQuery(); qDebug() << "DatabaseInterface::insertTracksList" << d->mSelectTracksMapping.boundValues(); qDebug() << "DatabaseInterface::insertTracksList" << d->mSelectTracksMapping.lastError(); d->mSelectTracksMapping.finish(); rollBackTransaction(); return; } bool isNewTrack = !d->mSelectTracksMapping.next(); if (isNewTrack) { insertTrackOrigin(oneTrack.resourceURI(), insertMusicSource(musicSource)); } else { updateTrackOrigin(d->mSelectTracksMapping.record().value(0).toULongLong(), oneTrack.resourceURI()); } d->mSelectTracksMapping.finish(); const auto insertedTrackId = internalInsertTrack(oneTrack, covers, 0, modifiedAlbumIds, (isNewTrack ? TrackFileInsertType::NewTrackFileInsert : TrackFileInsertType::ModifiedTrackFileInsert)); if (isNewTrack && insertedTrackId != 0) { insertedTracks.push_back(insertedTrackId); } if (d->mStopRequest == 1) { transactionResult = finishTransaction(); if (!transactionResult) { return; } return; } } const auto &constModifiedAlbumIds = modifiedAlbumIds; for (auto albumId : constModifiedAlbumIds) { Q_EMIT albumModified(internalAlbumFromId(albumId), albumId); } QList newTracks; for (auto trackId : qAsConst(insertedTracks)) { newTracks.push_back(internalTrackFromDatabaseId(trackId)); } if (!newTracks.isEmpty()) { Q_EMIT tracksAdded(newTracks); } transactionResult = finishTransaction(); if (!transactionResult) { return; } } void DatabaseInterface::removeTracksList(const QList &removedTracks) { auto transactionResult = startTransaction(); if (!transactionResult) { return; } internalRemoveTracksList(removedTracks); transactionResult = finishTransaction(); if (!transactionResult) { return; } } void DatabaseInterface::modifyTracksList(const QList &modifiedTracks, const QHash &covers, const QString &musicSource) { auto transactionResult = startTransaction(); if (!transactionResult) { return; } QSet modifiedAlbumIds; for (const auto &oneModifiedTrack : modifiedTracks) { if (oneModifiedTrack.albumArtist().isEmpty()) { continue; } bool modifyExistingTrack = internalTrackFromDatabaseId(oneModifiedTrack.databaseId()).isValid() || (internalTrackIdFromFileName(oneModifiedTrack.resourceURI()) != 0); auto originTrackId = oneModifiedTrack.databaseId(); if (!originTrackId) { originTrackId = internalTrackIdFromFileName(oneModifiedTrack.resourceURI()); } if (!modifyExistingTrack) { insertTrackOrigin(oneModifiedTrack.resourceURI(), insertMusicSource(musicSource)); } else { updateTrackOrigin(originTrackId, oneModifiedTrack.resourceURI()); } internalInsertTrack(oneModifiedTrack, covers, (modifyExistingTrack ? originTrackId : 0), modifiedAlbumIds, (modifyExistingTrack ? TrackFileInsertType::ModifiedTrackFileInsert : TrackFileInsertType::NewTrackFileInsert)); } const auto &constModifiedAlbumIds = modifiedAlbumIds; for (auto albumId : constModifiedAlbumIds) { Q_EMIT albumModified(internalAlbumFromId(albumId), albumId); } transactionResult = finishTransaction(); if (!transactionResult) { return; } } bool DatabaseInterface::startTransaction() const { auto result = false; auto transactionResult = d->mTracksDatabase.transaction(); if (!transactionResult) { qDebug() << "transaction failed" << d->mTracksDatabase.lastError() << d->mTracksDatabase.lastError().driverText(); return result; } result = true; return result; } bool DatabaseInterface::finishTransaction() const { auto result = false; auto transactionResult = d->mTracksDatabase.commit(); if (!transactionResult) { qDebug() << "commit failed" << d->mTracksDatabase.lastError() << d->mTracksDatabase.lastError().nativeErrorCode(); return result; } result = true; return result; } bool DatabaseInterface::rollBackTransaction() const { auto result = false; auto transactionResult = d->mTracksDatabase.rollback(); if (!transactionResult) { qDebug() << "commit failed" << d->mTracksDatabase.lastError() << d->mTracksDatabase.lastError().nativeErrorCode(); return result; } result = true; return result; } void DatabaseInterface::initDatabase() const { auto transactionResult = startTransaction(); if (!transactionResult) { return; } auto listTables = d->mTracksDatabase.tables(); if (!listTables.contains(QStringLiteral("DatabaseVersionV2"))) { for (const auto &oneTable : listTables) { QSqlQuery createSchemaQuery(d->mTracksDatabase); auto result = createSchemaQuery.exec(QStringLiteral("DROP TABLE ") + oneTable); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastError(); } } listTables = d->mTracksDatabase.tables(); } if (!listTables.contains(QStringLiteral("DatabaseVersionV2"))) { QSqlQuery createSchemaQuery(d->mTracksDatabase); const auto &result = createSchemaQuery.exec(QStringLiteral("CREATE TABLE `DatabaseVersionV2` (`Version` INTEGER PRIMARY KEY NOT NULL)")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastError(); } } if (!listTables.contains(QStringLiteral("DiscoverSource"))) { QSqlQuery createSchemaQuery(d->mTracksDatabase); const auto &result = createSchemaQuery.exec(QStringLiteral("CREATE TABLE `DiscoverSource` (`ID` INTEGER PRIMARY KEY NOT NULL, " "`Name` VARCHAR(55) NOT NULL, " "UNIQUE (`Name`))")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastError(); } } if (!listTables.contains(QStringLiteral("Artists"))) { QSqlQuery createSchemaQuery(d->mTracksDatabase); const auto &result = createSchemaQuery.exec(QStringLiteral("CREATE TABLE `Artists` (`ID` INTEGER PRIMARY KEY NOT NULL, " "`Name` VARCHAR(55) NOT NULL, " "UNIQUE (`Name`))")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastError(); } } if (!listTables.contains(QStringLiteral("Albums"))) { QSqlQuery createSchemaQuery(d->mTracksDatabase); const auto &result = createSchemaQuery.exec(QStringLiteral("CREATE TABLE `Albums` (" "`ID` INTEGER PRIMARY KEY NOT NULL, " "`Title` VARCHAR(55) NOT NULL, " "`CoverFileName` VARCHAR(255) NOT NULL, " "`TracksCount` INTEGER NOT NULL, " "`IsSingleDiscAlbum` BOOLEAN NOT NULL, " "`AlbumInternalID` VARCHAR(55))")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastError(); } } if (!listTables.contains(QStringLiteral("AlbumsArtists"))) { QSqlQuery createSchemaQuery(d->mTracksDatabase); const auto &result = createSchemaQuery.exec(QStringLiteral("CREATE TABLE `AlbumsArtists` (" "`AlbumID` INTEGER NOT NULL, " "`ArtistID` INTEGER NOT NULL, " "CONSTRAINT pk_albumsartists PRIMARY KEY (`AlbumID`, `ArtistID`), " "CONSTRAINT fk_albums FOREIGN KEY (`AlbumID`) REFERENCES `Albums`(`ID`) " "ON DELETE CASCADE, " "CONSTRAINT fk_artists FOREIGN KEY (`ArtistID`) REFERENCES `Artists`(`ID`) " "ON DELETE CASCADE)")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastError(); } } if (!listTables.contains(QStringLiteral("Tracks"))) { QSqlQuery createSchemaQuery(d->mTracksDatabase); const auto &result = createSchemaQuery.exec(QStringLiteral("CREATE TABLE `Tracks` (" "`ID` INTEGER PRIMARY KEY NOT NULL, " "`Title` VARCHAR(85) NOT NULL, " "`AlbumID` INTEGER NOT NULL, " "`TrackNumber` INTEGER NOT NULL, " "`DiscNumber` INTEGER DEFAULT -1, " "`Duration` INTEGER NOT NULL, " "`Rating` INTEGER NOT NULL DEFAULT 0, " "`Genre` VARCHAR(85) DEFAULT '', " "`Composer` VARCHAR(85) DEFAULT '', " "`Lyricist` VARCHAR(85) DEFAULT '', " "`Comment` VARCHAR(85) DEFAULT '', " "`Year` INTEGER DEFAULT 0, " "`Channels` INTEGER DEFAULT -1, " "`BitRate` INTEGER DEFAULT -1, " "`SampleRate` INTEGER DEFAULT -1, " "UNIQUE (`Title`, `AlbumID`, `TrackNumber`, `DiscNumber`), " "CONSTRAINT fk_tracks_album FOREIGN KEY (`AlbumID`) REFERENCES `Albums`(`ID`))")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastError(); } } if (!listTables.contains(QStringLiteral("TracksArtists"))) { QSqlQuery createSchemaQuery(d->mTracksDatabase); const auto &result = createSchemaQuery.exec(QStringLiteral("CREATE TABLE `TracksArtists` (" "`TrackID` INTEGER NOT NULL, " "`ArtistID` INTEGER NOT NULL, " "CONSTRAINT pk_tracksartists PRIMARY KEY (`TrackID`, `ArtistID`), " "CONSTRAINT fk_tracks FOREIGN KEY (`TrackID`) REFERENCES `Tracks`(`ID`) " "ON DELETE CASCADE, " "CONSTRAINT fk_artists FOREIGN KEY (`ArtistID`) REFERENCES `Artists`(`ID`) " "ON DELETE CASCADE)")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastError(); } } if (!listTables.contains(QStringLiteral("TracksMapping"))) { QSqlQuery createSchemaQuery(d->mTracksDatabase); const auto &result = createSchemaQuery.exec(QStringLiteral("CREATE TABLE `TracksMapping` (" "`TrackID` INTEGER NULL, " "`DiscoverID` INTEGER NOT NULL, " "`FileName` VARCHAR(255) NOT NULL, " "`Priority` INTEGER NOT NULL, " "`TrackValid` BOOLEAN NOT NULL, " "PRIMARY KEY (`FileName`), " "CONSTRAINT TracksUnique UNIQUE (`TrackID`, `Priority`), " "CONSTRAINT fk_tracksmapping_trackID FOREIGN KEY (`TrackID`) REFERENCES `Tracks`(`ID`), " "CONSTRAINT fk_tracksmapping_discoverID FOREIGN KEY (`DiscoverID`) REFERENCES `DiscoverSource`(`ID`))")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastError(); } } { QSqlQuery createTrackIndex(d->mTracksDatabase); const auto &result = createTrackIndex.exec(QStringLiteral("CREATE INDEX " "IF NOT EXISTS " "`TracksAlbumIndex` ON `Tracks` " "(`AlbumID`)")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createTrackIndex.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createTrackIndex.lastError(); } } { QSqlQuery createTrackIndex(d->mTracksDatabase); const auto &result = createTrackIndex.exec(QStringLiteral("CREATE INDEX " "IF NOT EXISTS " "`AlbumsArtistsArtistIndex` ON `AlbumsArtists` " "(`ArtistID`)")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createTrackIndex.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createTrackIndex.lastError(); } } { QSqlQuery createTrackIndex(d->mTracksDatabase); const auto &result = createTrackIndex.exec(QStringLiteral("CREATE INDEX " "IF NOT EXISTS " "`AlbumsArtistsAlbumIndex` ON `AlbumsArtists` " "(`AlbumID`)")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createTrackIndex.lastError(); } } { QSqlQuery createTrackIndex(d->mTracksDatabase); const auto &result = createTrackIndex.exec(QStringLiteral("CREATE INDEX " "IF NOT EXISTS " "`TracksArtistsArtistIndex` ON `TracksArtists` " "(`ArtistID`)")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createTrackIndex.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createTrackIndex.lastError(); } } { QSqlQuery createTrackIndex(d->mTracksDatabase); const auto &result = createTrackIndex.exec(QStringLiteral("CREATE INDEX " "IF NOT EXISTS " "`TracksArtistsTrackIndex` ON `TracksArtists` " "(`TrackID`)")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createTrackIndex.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createTrackIndex.lastError(); } } { QSqlQuery createTrackIndex(d->mTracksDatabase); const auto &result = createTrackIndex.exec(QStringLiteral("CREATE INDEX " "IF NOT EXISTS " "`TracksFileNameIndex` ON `TracksMapping` " "(`FileName`)")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createTrackIndex.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createTrackIndex.lastError(); } } transactionResult = finishTransaction(); if (!transactionResult) { return; } } void DatabaseInterface::initRequest() { auto transactionResult = startTransaction(); if (!transactionResult) { return; } { auto selectAlbumQueryText = QStringLiteral("SELECT " "album.`ID`, " "album.`Title`, " "album.`AlbumInternalID`, " "artist.`Name`, " "album.`CoverFileName`, " "album.`TracksCount`, " "album.`IsSingleDiscAlbum` " "FROM `Albums` album " "LEFT JOIN `AlbumsArtists` albumArtist " "ON " "albumArtist.`AlbumID` = album.`ID` " "LEFT JOIN `Artists` artist " "ON " "albumArtist.`ArtistID` = artist.`ID` " "WHERE " "album.`ID` = :albumId"); auto result = d->mSelectAlbumQuery.prepare(selectAlbumQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumQuery.lastError(); } } { auto selectAllAlbumsText = QStringLiteral("SELECT " "album.`ID`, " "album.`Title`, " "album.`AlbumInternalID`, " "artist.`Name`, " "album.`CoverFileName`, " "album.`TracksCount`, " "album.`IsSingleDiscAlbum` " "FROM `Albums` album " "LEFT JOIN `AlbumsArtists` albumArtist " "ON " "albumArtist.`AlbumID` = album.`ID` " "LEFT JOIN `Artists` artist " "ON " "albumArtist.`ArtistID` = artist.`ID` " "ORDER BY album.`Title`"); auto result = d->mSelectAllAlbumsQuery.prepare(selectAllAlbumsText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllAlbumsQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllAlbumsQuery.lastError(); } } { auto selectAllArtistsWithFilterText = QStringLiteral("SELECT `ID`, " "`Name` " "FROM `Artists`"); auto result = d->mSelectAllArtistsQuery.prepare(selectAllArtistsWithFilterText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllArtistsQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllArtistsQuery.lastError(); } } { auto selectAllTracksText = QStringLiteral("SELECT " "tracks.`ID`, " "tracks.`Title`, " "tracks.`AlbumID`, " "artist.`Name`, " "artistAlbum.`Name`, " "tracksMapping.`FileName`, " "tracks.`TrackNumber`, " "tracks.`DiscNumber`, " "tracks.`Duration`, " "album.`Title`, " "tracks.`Rating`, " "album.`CoverFileName`, " "album.`IsSingleDiscAlbum`, " "tracks.`Genre`, " "tracks.`Composer`, " "tracks.`Lyricist`, " "tracks.`Comment`, " "tracks.`Year`, " "tracks.`Channels`, " "tracks.`BitRate`, " "tracks.`SampleRate` " "FROM " "`Tracks` tracks, `Artists` artist, `TracksArtists` trackArtist, " "`Albums` album, `TracksMapping` tracksMapping " "LEFT JOIN `AlbumsArtists` artistAlbumMapping ON artistAlbumMapping.`AlbumID` = album.`ID` " "LEFT JOIN `Artists` artistAlbum ON artistAlbum.`ID` = artistAlbumMapping.`ArtistID` " "WHERE " "tracks.`ID` = trackArtist.`TrackID` AND " "artist.`ID` = trackArtist.`ArtistID` AND " "tracks.`AlbumID` = album.`ID` AND " "tracksMapping.`TrackID` = tracks.`ID` AND " "tracksMapping.`Priority` = (SELECT MIN(`Priority`) FROM `TracksMapping` WHERE `TrackID` = tracks.`ID`)"); auto result = d->mSelectAllTracksQuery.prepare(selectAllTracksText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllTracksQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllTracksQuery.lastError(); } } { auto selectAllInvalidTracksFromSourceQueryText = QStringLiteral("SELECT " "tracks.`ID`, " "tracks.`Title`, " "tracks.`AlbumID`, " "artist.`Name`, " "artistAlbum.`Name`, " "tracksMapping.`FileName`, " "tracks.`TrackNumber`, " "tracks.`DiscNumber`, " "tracks.`Duration`, " "album.`Title`, " "tracks.`Rating`, " "album.`CoverFileName`, " "album.`IsSingleDiscAlbum`, " "tracks.`Genre`, " "tracks.`Composer`, " "tracks.`Lyricist`, " "tracks.`Comment`, " "tracks.`Year`, " "tracks.`Channels`, " "tracks.`BitRate`, " "tracks.`SampleRate` " "FROM " "`Tracks` tracks, `Artists` artist, `TracksArtists` trackArtist, " "`Albums` album, `TracksMapping` tracksMapping, `DiscoverSource` source " "LEFT JOIN `AlbumsArtists` artistAlbumMapping ON artistAlbumMapping.`AlbumID` = album.`ID` " "LEFT JOIN `Artists` artistAlbum ON artistAlbum.`ID` = artistAlbumMapping.`ArtistID` " "WHERE " "tracks.`ID` = trackArtist.`TrackID` AND " "artist.`ID` = trackArtist.`ArtistID` AND " "tracks.`AlbumID` = album.`ID` AND " "source.`Name` = :source AND " "source.`ID` = tracksMapping.`DiscoverID` AND " "tracksMapping.`TrackValid` = 0 AND " "tracksMapping.`TrackID` = tracks.`ID`"); auto result = d->mSelectAllInvalidTracksFromSourceQuery.prepare(selectAllInvalidTracksFromSourceQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllInvalidTracksFromSourceQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllInvalidTracksFromSourceQuery.lastError(); } } { auto selectAllTracksFromSourceQueryText = QStringLiteral("SELECT " "tracks.`ID`, " "tracks.`Title`, " "tracks.`AlbumID`, " "artist.`Name`, " "artistAlbum.`Name`, " "tracksMapping.`FileName`, " "tracks.`TrackNumber`, " "tracks.`DiscNumber`, " "tracks.`Duration`, " "album.`Title`, " "tracks.`Rating`, " "album.`CoverFileName`, " "album.`IsSingleDiscAlbum`, " "tracks.`Genre`, " "tracks.`Composer`, " "tracks.`Lyricist`, " "tracks.`Comment`, " "tracks.`Year`, " "tracks.`Channels`, " "tracks.`BitRate`, " "tracks.`SampleRate` " "FROM " "`Tracks` tracks, `Artists` artist, `TracksArtists` trackArtist, " "`Albums` album, `TracksMapping` tracksMapping, `DiscoverSource` source " "LEFT JOIN `AlbumsArtists` artistAlbumMapping ON artistAlbumMapping.`AlbumID` = album.`ID` " "LEFT JOIN `Artists` artistAlbum ON artistAlbum.`ID` = artistAlbumMapping.`ArtistID` " "WHERE " "tracks.`ID` = trackArtist.`TrackID` AND " "artist.`ID` = trackArtist.`ArtistID` AND " "tracks.`AlbumID` = album.`ID` AND " "source.`Name` = :source AND " "source.`ID` = tracksMapping.`DiscoverID` AND " "tracksMapping.`TrackID` = tracks.`ID` AND " "tracksMapping.`Priority` = (SELECT MIN(`Priority`) FROM `TracksMapping` WHERE `TrackID` = tracks.`ID`)"); auto result = d->mSelectAllTracksFromSourceQuery.prepare(selectAllTracksFromSourceQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllTracksFromSourceQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllTracksFromSourceQuery.lastError(); } } { auto selectArtistByNameText = QStringLiteral("SELECT `ID`, " "`Name` " "FROM `Artists` " "WHERE " "`Name` = :name"); auto result = d->mSelectArtistByNameQuery.prepare(selectArtistByNameText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectArtistByNameQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectArtistByNameQuery.lastError(); } } { auto insertArtistsText = QStringLiteral("INSERT INTO `Artists` (`ID`, `Name`) " "VALUES (:artistId, :name)"); auto result = d->mInsertArtistsQuery.prepare(insertArtistsText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mInsertArtistsQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mInsertArtistsQuery.lastError(); } } { auto selectTrackQueryText = QStringLiteral("SELECT " "tracks.`ID`, " "tracks.`Title`, " "tracks.`AlbumID`, " "artist.`Name`, " "artistAlbum.`Name`, " "tracksMapping.`FileName`, " "tracks.`TrackNumber`, " "tracks.`DiscNumber`, " "tracks.`Duration`, " "album.`Title`, " "tracks.`Rating`, " "album.`CoverFileName`, " "album.`IsSingleDiscAlbum`, " "tracks.`Genre`, " "tracks.`Composer`, " "tracks.`Lyricist`, " "tracks.`Comment`, " "tracks.`Year`, " "tracks.`Channels`, " "tracks.`BitRate`, " "tracks.`SampleRate` " "FROM " "`Tracks` tracks, `Artists` artist, `TracksArtists` trackArtist, " "`Albums` album, `TracksMapping` tracksMapping " "LEFT JOIN `AlbumsArtists` artistAlbumMapping ON artistAlbumMapping.`AlbumID` = album.`ID` " "LEFT JOIN `Artists` artistAlbum ON artistAlbum.`ID` = artistAlbumMapping.`ArtistID` " "WHERE " "tracks.`ID` = trackArtist.`TrackID` AND " "artist.`ID` = trackArtist.`ArtistID` AND " "tracksMapping.`TrackID` = tracks.`ID` AND " "tracks.`AlbumID` = :albumId AND " "album.`ID` = :albumId AND " "tracksMapping.`Priority` = (SELECT MIN(`Priority`) FROM `TracksMapping` WHERE `TrackID` = tracks.`ID`) " "ORDER BY tracks.`DiscNumber` ASC, " "tracks.`TrackNumber` ASC"); auto result = d->mSelectTrackQuery.prepare(selectTrackQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackQuery.lastError(); } } { auto selectTrackFromIdQueryText = QStringLiteral("SELECT " "tracks.`Id`, " "tracks.`Title`, " "tracks.`AlbumID`, " "artist.`Name`, " "artistAlbum.`Name`, " "tracksMapping.`FileName`, " "tracks.`TrackNumber`, " "tracks.`DiscNumber`, " "tracks.`Duration`, " "album.`Title`, " "tracks.`Rating`, " "album.`CoverFileName`, " "album.`IsSingleDiscAlbum`, " "tracks.`Genre`, " "tracks.`Composer`, " "tracks.`Lyricist`, " "tracks.`Comment`, " "tracks.`Year`, " "tracks.`Channels`, " "tracks.`BitRate`, " "tracks.`SampleRate` " "FROM " "`Tracks` tracks, `Artists` artist, `TracksArtists` trackArtist, " "`Albums` album, `TracksMapping` tracksMapping " "LEFT JOIN `AlbumsArtists` artistAlbumMapping ON artistAlbumMapping.`AlbumID` = album.`ID` " "LEFT JOIN `Artists` artistAlbum ON artistAlbum.`ID` = artistAlbumMapping.`ArtistID` " "WHERE " "tracks.`ID` = trackArtist.`TrackID` AND " "artist.`ID` = trackArtist.`ArtistID` AND " "tracks.`ID` = :trackId AND " "tracks.`AlbumID` = album.`ID` AND " "tracksMapping.`TrackID` = tracks.`ID` AND " "tracksMapping.`Priority` = (SELECT MIN(`Priority`) FROM `TracksMapping` WHERE `TrackID` = tracks.`ID`)"); auto result = d->mSelectTrackFromIdQuery.prepare(selectTrackFromIdQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackFromIdQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackFromIdQuery.lastError(); } } { auto selectCountAlbumsQueryText = QStringLiteral("SELECT count(*) " "FROM `Albums` album, `Artists` artist, `AlbumsArtists` albumArtist " "WHERE artist.`Name` = :artistName AND " "album.`ID` = albumArtist.`AlbumID` AND " "artist.`ID` = albumArtist.`ArtistID`"); const auto result = d->mSelectCountAlbumsForArtistQuery.prepare(selectCountAlbumsQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectCountAlbumsForArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectCountAlbumsForArtistQuery.lastError(); } } { auto selectAlbumIdFromTitleQueryText = QStringLiteral("SELECT " "album.`ID` " "FROM " "`Albums` album, `Artists` artist, `AlbumsArtists` albumArtist " "WHERE " "artist.`Name` = :artistName AND " "album.`ID` = albumArtist.`AlbumID` AND " "artist.`ID` = albumArtist.`ArtistID` AND " "album.`Title` = :title"); auto result = d->mSelectAlbumIdFromTitleQuery.prepare(selectAlbumIdFromTitleQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumIdFromTitleQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumIdFromTitleQuery.lastError(); } } { auto selectAlbumIdFromTitleAndArtistQueryText = QStringLiteral("SELECT " "album.`ID` " "FROM " "`Albums` album, " "`AlbumsArtists` albumArtist " "WHERE " "album.`ID` = albumArtist.`AlbumID` AND " "album.`Title` = :title AND " "albumArtist.`ArtistID` = :artistId"); auto result = d->mSelectAlbumIdFromTitleAndArtistQuery.prepare(selectAlbumIdFromTitleAndArtistQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumIdFromTitleAndArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumIdFromTitleAndArtistQuery.lastError(); } } { auto selectAlbumIdFromTitleWithoutArtistQueryText = QStringLiteral("SELECT " "album.`ID` " "FROM " "`Albums` album " "WHERE " "album.`Title` = :title AND " "NOT EXISTS (" "SELECT " "albumArtist.`AlbumID` " "FROM " "`AlbumsArtists` albumArtist " "WHERE " "albumArtist.`AlbumID` = album.`ID`" ")"); auto result = d->mSelectAlbumIdFromTitleWithoutArtistQuery.prepare(selectAlbumIdFromTitleWithoutArtistQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumIdFromTitleWithoutArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumIdFromTitleWithoutArtistQuery.lastError(); } } { auto insertAlbumQueryText = QStringLiteral("INSERT INTO Albums (`ID`, `Title`, `CoverFileName`, `TracksCount`, `IsSingleDiscAlbum`) " "VALUES (:albumId, :title, :coverFileName, :tracksCount, :isSingleDiscAlbum)"); auto result = d->mInsertAlbumQuery.prepare(insertAlbumQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mInsertAlbumQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mInsertAlbumQuery.lastError(); } } { auto insertAlbumArtistQueryText = QStringLiteral("INSERT INTO `AlbumsArtists` (`AlbumID`, `ArtistID`) " "VALUES (:albumId, :artistId)"); auto result = d->mInsertAlbumArtistQuery.prepare(insertAlbumArtistQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mInsertAlbumArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mInsertAlbumArtistQuery.lastError(); } } { auto insertTrackArtistQueryText = QStringLiteral("INSERT INTO `TracksArtists` (`TrackID`, `ArtistID`) " "VALUES (:trackId, :artistId)"); auto result = d->mInsertTrackArtistQuery.prepare(insertTrackArtistQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mInsertTrackArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mInsertTrackArtistQuery.lastError(); } } { auto insertTrackMappingQueryText = QStringLiteral("INSERT INTO `TracksMapping` (`FileName`, `DiscoverID`, `Priority`, `TrackValid`) " "VALUES (:fileName, :discoverId, :priority, 1)"); auto result = d->mInsertTrackMapping.prepare(insertTrackMappingQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mInsertTrackMapping.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mInsertTrackMapping.lastError(); } } { auto initialUpdateTracksValidityQueryText = QStringLiteral("UPDATE `TracksMapping` SET `TrackValid` = 0"); auto result = d->mInitialUpdateTracksValidity.prepare(initialUpdateTracksValidityQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mInitialUpdateTracksValidity.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mInitialUpdateTracksValidity.lastError(); } } { auto initialUpdateTracksValidityQueryText = QStringLiteral("UPDATE `TracksMapping` SET `TrackValid` = 1, `TrackID` = :trackId, `Priority` = :priority " "WHERE `FileName` = :fileName"); auto result = d->mUpdateTrackMapping.prepare(initialUpdateTracksValidityQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mUpdateTrackMapping.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mUpdateTrackMapping.lastError(); } } { auto removeTracksMappingFromSourceQueryText = QStringLiteral("DELETE FROM `TracksMapping` " "WHERE `FileName` = :fileName AND `DiscoverID` = :sourceId"); auto result = d->mRemoveTracksMappingFromSource.prepare(removeTracksMappingFromSourceQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mRemoveTracksMappingFromSource.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mRemoveTracksMappingFromSource.lastError(); } } { auto removeTracksMappingQueryText = QStringLiteral("DELETE FROM `TracksMapping` " "WHERE `FileName` = :fileName"); auto result = d->mRemoveTracksMapping.prepare(removeTracksMappingQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mRemoveTracksMapping.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mRemoveTracksMapping.lastError(); } } { auto selectTracksWithoutMappingQueryText = QStringLiteral("SELECT " "tracks.`Id`, " "tracks.`Title`, " "tracks.`AlbumID`, " "artist.`Name`, " "artistAlbum.`Name`, " "\"\" as FileName, " "tracks.`TrackNumber`, " "tracks.`DiscNumber`, " "tracks.`Duration`, " "album.`Title`, " "tracks.`Rating`, " "album.`CoverFileName`, " "album.`IsSingleDiscAlbum`, " "tracks.`Genre`, " "tracks.`Composer`, " "tracks.`Lyricist`, " "tracks.`Comment`, " "tracks.`Year`, " "tracks.`Channels`, " "tracks.`BitRate`, " "tracks.`SampleRate` " "FROM " "`Tracks` tracks, " "`Artists` artist, " "`TracksArtists` trackArtist, " "`Albums` album " "LEFT JOIN `AlbumsArtists` artistAlbumMapping ON artistAlbumMapping.`AlbumID` = album.`ID` " "LEFT JOIN `Artists` artistAlbum ON artistAlbum.`ID` = artistAlbumMapping.`ArtistID` " "WHERE " "tracks.`ID` = trackArtist.`TrackID` AND " "artist.`ID` = trackArtist.`ArtistID` AND " "tracks.`AlbumID` = album.`ID` AND " "tracks.`ID` NOT IN (SELECT tracksMapping2.`TrackID` FROM `TracksMapping` tracksMapping2)"); auto result = d->mSelectTracksWithoutMappingQuery.prepare(selectTracksWithoutMappingQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTracksWithoutMappingQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectTracksWithoutMappingQuery.lastError(); } } { auto selectTracksMappingQueryText = QStringLiteral("SELECT " "`TrackID`, " "`FileName`, " "`DiscoverID`, " "`Priority` " "FROM " "`TracksMapping` " "WHERE " "`FileName` = :fileName"); auto result = d->mSelectTracksMapping.prepare(selectTracksMappingQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTracksMapping.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectTracksMapping.lastError(); } } { auto selectTracksMappingPriorityQueryText = QStringLiteral("SELECT " "`Priority` " "FROM " "`TracksMapping` " "WHERE " "`TrackID` = :trackId AND " "`FileName` = :fileName"); auto result = d->mSelectTracksMappingPriority.prepare(selectTracksMappingPriorityQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTracksMappingPriority.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectTracksMappingPriority.lastError(); } } { auto selectTracksMappingPriorityQueryByTrackIdText = QStringLiteral("SELECT " "MAX(`Priority`) " "FROM " "`TracksMapping` " "WHERE " "`TrackID` = :trackId"); auto result = d->mSelectTracksMappingPriorityByTrackId.prepare(selectTracksMappingPriorityQueryByTrackIdText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTracksMappingPriorityByTrackId.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectTracksMappingPriorityByTrackId.lastError(); } } { auto selectAllTrackFilesFromSourceQueryText = QStringLiteral("SELECT " "tracksMapping.`FileName` " "FROM " "`TracksMapping` tracksMapping " "WHERE " "tracksMapping.`DiscoverID` = :discoverId"); auto result = d->mSelectAllTrackFilesFromSourceQuery.prepare(selectAllTrackFilesFromSourceQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllTrackFilesFromSourceQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllTrackFilesFromSourceQuery.lastError(); } } { auto findInvalidTrackFilesText = QStringLiteral("SELECT " "tracksMapping.`FileName`, " "tracksMapping.`DiscoverID` " "FROM " "`TracksMapping` tracksMapping " "WHERE " "tracksMapping.`TrackValid` = 0"); auto result = d->mFindInvalidTrackFilesQuery.prepare(findInvalidTrackFilesText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mFindInvalidTrackFilesQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mFindInvalidTrackFilesQuery.lastError(); } } { auto insertMusicSourceQueryText = QStringLiteral("INSERT OR IGNORE INTO `DiscoverSource` (`ID`, `Name`) " "VALUES (:discoverId, :name)"); auto result = d->mInsertMusicSource.prepare(insertMusicSourceQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mInsertMusicSource.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mInsertMusicSource.lastError(); } } { auto selectMusicSourceQueryText = QStringLiteral("SELECT `ID` FROM `DiscoverSource` WHERE `Name` = :name"); auto result = d->mSelectMusicSource.prepare(selectMusicSourceQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectMusicSource.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectMusicSource.lastError(); } } { auto selectTrackQueryText = QStringLiteral("SELECT " "tracks.`ID`, tracksMapping.`FileName` " "FROM " "`Tracks` tracks, `Artists` artist, `TracksArtists` trackArtist, " "`TracksMapping` tracksMapping " "WHERE " "tracks.`Title` = :title AND " "tracks.`AlbumID` = :album AND " "artist.`Name` = :artist AND " "tracks.`ID` = trackArtist.`TrackID` AND " "artist.`ID` = trackArtist.`ArtistID` AND " "tracksMapping.`TrackID` = tracks.`ID` AND " "tracksMapping.`Priority` = (SELECT MIN(`Priority`) FROM `TracksMapping` WHERE `TrackID` = tracks.`ID`)"); auto result = d->mSelectTrackIdFromTitleAlbumIdArtistQuery.prepare(selectTrackQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackIdFromTitleAlbumIdArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackIdFromTitleAlbumIdArtistQuery.lastError(); } auto insertTrackQueryText = QStringLiteral("INSERT INTO `Tracks` (`ID`, `Title`, `AlbumID`, `Genre`, `Composer`, `Lyricist`, `Comment`, `TrackNumber`, `DiscNumber`, `Channels`, `BitRate`, `SampleRate`, `Year`, `Duration`, `Rating` ) " "VALUES (:trackId, :title, :album, :genre, :composer, :lyricist, :comment, :trackNumber, :discNumber, :channels, :bitRate, :sampleRate, :year, :trackDuration, :trackRating)"); result = d->mInsertTrackQuery.prepare(insertTrackQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mInsertTrackQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mInsertTrackQuery.lastError(); } } { auto selectTrackQueryText = QStringLiteral("SELECT " "tracks.ID " "FROM " "`Tracks` tracks, " "`Albums` albums, " "`TracksArtists` trackArtist, " "`Artists` artist " "WHERE " "tracks.`Title` = :title AND " "tracks.`AlbumID` = albums.`ID` AND " "albums.`Title` = :album AND " "tracks.`TrackNumber` = :trackNumber AND " "tracks.`DiscNumber` = :discNumber AND " "trackArtist.`TrackID` = tracks.`ID` AND " "trackArtist.`ArtistID` = artist.`ID` AND " "artist.`Name` = :artist"); auto result = d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.prepare(selectTrackQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.lastError(); } } { auto selectTrackQueryText = QStringLiteral("SELECT " "tracks.ID " "FROM " "`Tracks` tracks, " "`Albums` albums " "WHERE " "tracks.`Title` = :title AND " "tracks.`AlbumID` = albums.`ID` AND " "albums.`Title` = :album AND " "tracks.`TrackNumber` = :trackNumber AND " "tracks.`DiscNumber` = :discNumber AND " "( " "( NOT EXISTS (SELECT albumArtistMapping.`AlbumID` " "FROM " "`AlbumsArtists` albumArtistMapping " "WHERE " "albumArtistMapping.`AlbumID` = albums.`ID`) " ") OR " "( EXISTS (SELECT albumArtistMapping.`AlbumID` " "FROM " "`AlbumsArtists` albumArtistMapping, " "`Artists` albumArtist " "WHERE " "albumArtist.`Name` = :albumArtist AND " "albumArtist.`ID` = albumArtistMapping.`ArtistID` AND " "albumArtistMapping.`AlbumID` = albums.`ID`) " ") " ")"); auto result = d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.prepare(selectTrackQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.lastError(); } } + { + auto selectAlbumArtUriFromAlbumIdQueryText = QStringLiteral("SELECT `CoverFileName`" + "FROM " + "`Albums` " + "WHERE " + "`ID` = :albumId"); + + auto result = d->mSelectAlbumArtUriFromAlbumIdQuery.prepare(selectAlbumArtUriFromAlbumIdQueryText); + + if (!result) { + qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumArtUriFromAlbumIdQuery.lastQuery(); + qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumArtUriFromAlbumIdQuery.lastError(); + } + } + { auto selectAlbumTrackCountQueryText = QStringLiteral("SELECT `TracksCount` " "FROM `Albums`" "WHERE " "`ID` = :albumId"); auto result = d->mSelectAlbumTrackCountQuery.prepare(selectAlbumTrackCountQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumTrackCountQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumTrackCountQuery.lastError(); } } { auto updateAlbumQueryText = QStringLiteral("UPDATE `Albums` " "SET `TracksCount` = (SELECT COUNT(*) FROM `Tracks` WHERE `AlbumID` = :albumId) " "WHERE " "`ID` = :albumId"); auto result = d->mUpdateAlbumQuery.prepare(updateAlbumQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mUpdateAlbumQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mUpdateAlbumQuery.lastError(); } } { auto updateIsSingleDiscAlbumFromIdQueryText = QStringLiteral("UPDATE `Albums` " "SET `IsSingleDiscAlbum` = (SELECT COUNT(DISTINCT DiscNumber) = 1 FROM `Tracks` WHERE `AlbumID` = :albumId) " "WHERE " "`ID` = :albumId"); auto result = d->mUpdateIsSingleDiscAlbumFromIdQuery.prepare(updateIsSingleDiscAlbumFromIdQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mUpdateIsSingleDiscAlbumFromIdQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mUpdateIsSingleDiscAlbumFromIdQuery.lastError(); } } { auto updateAlbumArtUriFromAlbumIdQueryText = QStringLiteral("UPDATE `Albums` " "SET `CoverFileName` = :coverFileName " "WHERE " "`ID` = :albumId"); auto result = d->mUpdateAlbumArtUriFromAlbumIdQuery.prepare(updateAlbumArtUriFromAlbumIdQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mUpdateAlbumArtUriFromAlbumIdQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mUpdateAlbumArtUriFromAlbumIdQuery.lastError(); } } { auto selectTracksFromArtistQueryText = QStringLiteral("SELECT " "tracks.`ID`, " "tracks.`Title`, " "tracks.`AlbumID`, " "artist.`Name`, " "artistAlbum.`Name`, " "tracksMapping.`FileName`, " "tracks.`TrackNumber`, " "tracks.`DiscNumber`, " "tracks.`Duration`, " "album.`Title`, " "tracks.`Rating`, " "album.`CoverFileName`, " "album.`IsSingleDiscAlbum`, " "tracks.`Genre`, " "tracks.`Composer`, " "tracks.`Lyricist`, " "tracks.`Comment`, " "tracks.`Year`, " "tracks.`Channels`, " "tracks.`BitRate`, " "tracks.`SampleRate` " "FROM `Tracks` tracks, `Albums` album, `Artists` artist, `TracksArtists` trackArtist, " "`TracksMapping` tracksMapping " "LEFT JOIN `AlbumsArtists` artistAlbumMapping ON artistAlbumMapping.`AlbumID` = album.`ID` " "LEFT JOIN `Artists` artistAlbum ON artistAlbum.`ID` = artistAlbumMapping.`ArtistID` " "WHERE " "artist.`Name` = :artistName AND " "tracks.`AlbumID` = album.`ID` AND " "artist.`ID` = trackArtist.`ArtistID` AND " "tracks.`ID` = trackArtist.`TrackID` AND " "tracksMapping.`TrackID` = tracks.`ID` AND " "tracksMapping.`Priority` = (SELECT MIN(`Priority`) FROM `TracksMapping` WHERE `TrackID` = tracks.`ID`) " "ORDER BY tracks.`Title` ASC, " "album.`Title` ASC"); auto result = d->mSelectTracksFromArtist.prepare(selectTracksFromArtistQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTracksFromArtist.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectTracksFromArtist.lastError(); } } { auto selectAlbumIdsFromArtistQueryText = QStringLiteral("SELECT " "album.`ID` " "FROM " "`Albums` album, " "`Artists` artist," "`AlbumsArtists` albumArtist " "WHERE " "album.`ID` = albumArtist.`AlbumID` AND " "artist.`ID` = albumArtist.`ArtistID` AND " "artist.`Name` = :artistName"); auto result = d->mSelectAlbumIdsFromArtist.prepare(selectAlbumIdsFromArtistQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumIdsFromArtist.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumIdsFromArtist.lastError(); } } { auto selectArtistQueryText = QStringLiteral("SELECT `ID`, " "`Name` " "FROM `Artists` " "WHERE " "`ID` = :artistId"); auto result = d->mSelectArtistQuery.prepare(selectArtistQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectArtistQuery.lastError(); } } { auto selectTrackFromFilePathQueryText = QStringLiteral("SELECT " "tracks.`ID`, " "tracks.`Title`, " "tracks.`AlbumID`, " "artist.`Name`, " "artistAlbum.`Name`, " "tracksMapping.`FileName`, " "tracks.`TrackNumber`, " "tracks.`DiscNumber`, " "tracks.`Duration`, " "album.`Title`, " "tracks.`Rating`, " "album.`CoverFileName`, " "album.`IsSingleDiscAlbum`, " "tracks.`Genre`, " "tracks.`Composer`, " "tracks.`Lyricist`, " "tracks.`Comment`, " "tracks.`Year`, " "tracks.`Channels`, " "tracks.`BitRate`, " "tracks.`SampleRate` " "FROM `Tracks` tracks, `Artists` artist, `Albums` album, `TracksArtists` trackArtist, " "`TracksMapping` tracksMapping " "LEFT JOIN `AlbumsArtists` artistAlbumMapping ON artistAlbumMapping.`AlbumID` = album.`ID` " "LEFT JOIN `Artists` artistAlbum ON artistAlbum.`ID` = artistAlbumMapping.`ArtistID` " "WHERE " "tracks.`AlbumID` = album.`ID` AND " "artist.`ID` = trackArtist.`ArtistID` AND " "tracks.`ID` = trackArtist.`TrackID` AND " "tracksMapping.`TrackID` = tracks.`ID` AND " "tracksMapping.`FileName` = :filePath AND " "tracksMapping.`Priority` = (SELECT MIN(`Priority`) FROM `TracksMapping` WHERE `TrackID` = tracks.`ID`)"); auto result = d->mSelectTrackFromFilePathQuery.prepare(selectTrackFromFilePathQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackFromFilePathQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackFromFilePathQuery.lastError(); } } { auto removeTrackQueryText = QStringLiteral("DELETE FROM `Tracks` " "WHERE " "`ID` = :trackId"); auto result = d->mRemoveTrackQuery.prepare(removeTrackQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mRemoveTrackQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mRemoveTrackQuery.lastError(); } } { auto removeTrackArtistQueryText = QStringLiteral("DELETE FROM `TracksArtists` " "WHERE " "`TrackID` = :trackId"); auto result = d->mRemoveTrackArtistQuery.prepare(removeTrackArtistQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mRemoveTrackArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mRemoveTrackArtistQuery.lastError(); } } { auto removeAlbumQueryText = QStringLiteral("DELETE FROM `Albums` " "WHERE " "`ID` = :albumId"); auto result = d->mRemoveAlbumQuery.prepare(removeAlbumQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mRemoveAlbumQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mRemoveAlbumQuery.lastError(); } } { auto removeAlbumArtistQueryText = QStringLiteral("DELETE FROM `AlbumsArtists` " "WHERE " "`AlbumID` = :albumId"); auto result = d->mRemoveAlbumArtistQuery.prepare(removeAlbumArtistQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mRemoveAlbumArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mRemoveAlbumArtistQuery.lastError(); } } { auto removeAlbumQueryText = QStringLiteral("DELETE FROM `Artists` " "WHERE " "`ID` = :artistId"); auto result = d->mRemoveArtistQuery.prepare(removeAlbumQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mRemoveArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mRemoveArtistQuery.lastError(); } } transactionResult = finishTransaction(); d->mInitFinished = true; Q_EMIT requestsInitDone(); } qulonglong DatabaseInterface::insertAlbum(const QString &title, const QString &albumArtist, const QString &trackArtist, const QUrl &albumArtURI, int tracksCount, bool isSingleDiscAlbum) { auto result = qulonglong(0); if (title.isEmpty()) { return result; } if (!albumArtist.isEmpty() || !trackArtist.isEmpty()) { d->mSelectAlbumIdFromTitleAndArtistQuery.bindValue(QStringLiteral(":title"), title); if (!albumArtist.isEmpty()) { d->mSelectAlbumIdFromTitleAndArtistQuery.bindValue(QStringLiteral(":artistId"), insertArtist(albumArtist)); } else { d->mSelectAlbumIdFromTitleAndArtistQuery.bindValue(QStringLiteral(":artistId"), insertArtist(trackArtist)); } auto queryResult = d->mSelectAlbumIdFromTitleAndArtistQuery.exec(); if (!queryResult || !d->mSelectAlbumIdFromTitleAndArtistQuery.isSelect() || !d->mSelectAlbumIdFromTitleAndArtistQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertAlbum" << d->mSelectAlbumIdFromTitleAndArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::insertAlbum" << d->mSelectAlbumIdFromTitleAndArtistQuery.boundValues(); qDebug() << "DatabaseInterface::insertAlbum" << d->mSelectAlbumIdFromTitleAndArtistQuery.lastError(); d->mSelectAlbumIdFromTitleAndArtistQuery.finish(); return result; } if (d->mSelectAlbumIdFromTitleAndArtistQuery.next()) { result = d->mSelectAlbumIdFromTitleAndArtistQuery.record().value(0).toULongLong(); d->mSelectAlbumIdFromTitleAndArtistQuery.finish(); return result; } d->mSelectAlbumIdFromTitleAndArtistQuery.finish(); } if (result == 0) { d->mSelectAlbumIdFromTitleWithoutArtistQuery.bindValue(QStringLiteral(":title"), title); auto queryResult = d->mSelectAlbumIdFromTitleWithoutArtistQuery.exec(); if (!queryResult || !d->mSelectAlbumIdFromTitleWithoutArtistQuery.isSelect() || !d->mSelectAlbumIdFromTitleWithoutArtistQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertAlbum" << d->mSelectAlbumIdFromTitleWithoutArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::insertAlbum" << d->mSelectAlbumIdFromTitleWithoutArtistQuery.boundValues(); qDebug() << "DatabaseInterface::insertAlbum" << d->mSelectAlbumIdFromTitleWithoutArtistQuery.lastError(); d->mSelectAlbumIdFromTitleWithoutArtistQuery.finish(); return result; } if (d->mSelectAlbumIdFromTitleWithoutArtistQuery.next()) { result = d->mSelectAlbumIdFromTitleWithoutArtistQuery.record().value(0).toULongLong(); d->mSelectAlbumIdFromTitleWithoutArtistQuery.finish(); return result; } d->mSelectAlbumIdFromTitleWithoutArtistQuery.finish(); } d->mInsertAlbumQuery.bindValue(QStringLiteral(":albumId"), d->mAlbumId); d->mInsertAlbumQuery.bindValue(QStringLiteral(":title"), title); d->mInsertAlbumQuery.bindValue(QStringLiteral(":coverFileName"), albumArtURI); d->mInsertAlbumQuery.bindValue(QStringLiteral(":tracksCount"), tracksCount); d->mInsertAlbumQuery.bindValue(QStringLiteral(":isSingleDiscAlbum"), isSingleDiscAlbum); auto queryResult = d->mInsertAlbumQuery.exec(); if (!queryResult || !d->mInsertAlbumQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertAlbum" << d->mInsertAlbumQuery.lastQuery(); qDebug() << "DatabaseInterface::insertAlbum" << d->mInsertAlbumQuery.boundValues(); qDebug() << "DatabaseInterface::insertAlbum" << d->mInsertAlbumQuery.lastError(); d->mInsertAlbumQuery.finish(); return result; } result = d->mAlbumId; d->mInsertAlbumQuery.finish(); if (!albumArtist.isEmpty()) { d->mInsertAlbumArtistQuery.bindValue(QStringLiteral(":albumId"), d->mAlbumId); d->mInsertAlbumArtistQuery.bindValue(QStringLiteral(":artistId"), insertArtist(albumArtist)); queryResult = d->mInsertAlbumArtistQuery.exec(); if (!queryResult || !d->mInsertAlbumArtistQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertAlbum" << d->mInsertAlbumArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::insertAlbum" << d->mInsertAlbumArtistQuery.boundValues(); qDebug() << "DatabaseInterface::insertAlbum" << d->mInsertAlbumArtistQuery.lastError(); d->mInsertAlbumArtistQuery.finish(); return result; } d->mInsertAlbumArtistQuery.finish(); } ++d->mAlbumId; Q_EMIT albumAdded(internalAlbumFromId(d->mAlbumId - 1)); return result; } bool DatabaseInterface::updateAlbumFromId(qulonglong albumId, const QUrl &albumArtUri, const MusicAudioTrack ¤tTrack) { auto modifiedAlbum = false; d->mUpdateIsSingleDiscAlbumFromIdQuery.bindValue(QStringLiteral(":albumId"), albumId); auto result = d->mUpdateIsSingleDiscAlbumFromIdQuery.exec(); if (!result || !d->mUpdateIsSingleDiscAlbumFromIdQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::updateIsSingleDiscAlbumFromId" << d->mUpdateIsSingleDiscAlbumFromIdQuery.lastQuery(); qDebug() << "DatabaseInterface::updateIsSingleDiscAlbumFromId" << d->mUpdateIsSingleDiscAlbumFromIdQuery.boundValues(); qDebug() << "DatabaseInterface::updateIsSingleDiscAlbumFromId" << d->mUpdateIsSingleDiscAlbumFromIdQuery.lastError(); d->mUpdateIsSingleDiscAlbumFromIdQuery.finish(); return modifiedAlbum; } modifiedAlbum = (d->mUpdateIsSingleDiscAlbumFromIdQuery.numRowsAffected() != 0); d->mUpdateIsSingleDiscAlbumFromIdQuery.finish(); if (!albumArtUri.isValid()) { return modifiedAlbum; } - const auto &album = internalAlbumFromId(albumId); + auto storedAlbumArtUri = internalAlbumArtUriFromAlbumId(albumId); - if (!album.albumArtURI().isValid() || album.albumArtURI() == albumArtUri) { + if (!storedAlbumArtUri.isValid() || storedAlbumArtUri == albumArtUri) { d->mUpdateAlbumArtUriFromAlbumIdQuery.bindValue(QStringLiteral(":albumId"), albumId); d->mUpdateAlbumArtUriFromAlbumIdQuery.bindValue(QStringLiteral(":coverFileName"), albumArtUri); result = d->mUpdateAlbumArtUriFromAlbumIdQuery.exec(); if (!result || !d->mUpdateAlbumArtUriFromAlbumIdQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::updateIsSingleDiscAlbumFromId" << d->mUpdateAlbumArtUriFromAlbumIdQuery.lastQuery(); qDebug() << "DatabaseInterface::updateIsSingleDiscAlbumFromId" << d->mUpdateAlbumArtUriFromAlbumIdQuery.boundValues(); qDebug() << "DatabaseInterface::updateIsSingleDiscAlbumFromId" << d->mUpdateAlbumArtUriFromAlbumIdQuery.lastError(); d->mUpdateAlbumArtUriFromAlbumIdQuery.finish(); return modifiedAlbum; } d->mUpdateAlbumArtUriFromAlbumIdQuery.finish(); modifiedAlbum = true; } - if (!album.isValidArtist() && album.canUpdateArtist(currentTrack)) { + if (!isValidArtist(albumId) && currentTrack.isValidAlbumArtist()) { d->mRemoveAlbumArtistQuery.bindValue(QStringLiteral(":albumId"), albumId); result = d->mRemoveAlbumArtistQuery.exec(); if (!result || !d->mRemoveAlbumArtistQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::updateIsSingleDiscAlbumFromId" << d->mRemoveAlbumArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::updateIsSingleDiscAlbumFromId" << d->mRemoveAlbumArtistQuery.boundValues(); qDebug() << "DatabaseInterface::updateIsSingleDiscAlbumFromId" << d->mRemoveAlbumArtistQuery.lastError(); d->mRemoveAlbumArtistQuery.finish(); return modifiedAlbum; } d->mRemoveAlbumArtistQuery.finish(); d->mInsertAlbumArtistQuery.bindValue(QStringLiteral(":albumId"), albumId); d->mInsertAlbumArtistQuery.bindValue(QStringLiteral(":artistId"), insertArtist(currentTrack.albumArtist())); result = d->mInsertAlbumArtistQuery.exec(); if (!result || !d->mInsertAlbumArtistQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::updateIsSingleDiscAlbumFromId" << d->mInsertAlbumArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::updateIsSingleDiscAlbumFromId" << d->mInsertAlbumArtistQuery.boundValues(); qDebug() << "DatabaseInterface::updateIsSingleDiscAlbumFromId" << d->mInsertAlbumArtistQuery.lastError(); d->mInsertAlbumArtistQuery.finish(); return modifiedAlbum; } d->mInsertAlbumArtistQuery.finish(); modifiedAlbum = true; } return modifiedAlbum; } qulonglong DatabaseInterface::insertArtist(const QString &name) { auto result = qulonglong(0); if (name.isEmpty()) { return result; } d->mSelectArtistByNameQuery.bindValue(QStringLiteral(":name"), name); auto queryResult = d->mSelectArtistByNameQuery.exec(); if (!queryResult || !d->mSelectArtistByNameQuery.isSelect() || !d->mSelectArtistByNameQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertArtist" << d->mSelectArtistByNameQuery.lastQuery(); qDebug() << "DatabaseInterface::insertArtist" << d->mSelectArtistByNameQuery.boundValues(); qDebug() << "DatabaseInterface::insertArtist" << d->mSelectArtistByNameQuery.lastError(); d->mSelectArtistByNameQuery.finish(); return result; } if (d->mSelectArtistByNameQuery.next()) { result = d->mSelectArtistByNameQuery.record().value(0).toULongLong(); d->mSelectArtistByNameQuery.finish(); return result; } d->mSelectArtistByNameQuery.finish(); d->mInsertArtistsQuery.bindValue(QStringLiteral(":artistId"), d->mArtistId); d->mInsertArtistsQuery.bindValue(QStringLiteral(":name"), name); queryResult = d->mInsertArtistsQuery.exec(); if (!queryResult || !d->mInsertArtistsQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertArtist" << d->mInsertArtistsQuery.lastQuery(); qDebug() << "DatabaseInterface::insertArtist" << d->mInsertArtistsQuery.boundValues(); qDebug() << "DatabaseInterface::insertArtist" << d->mInsertArtistsQuery.lastError(); d->mInsertArtistsQuery.finish(); return result; } result = d->mArtistId; ++d->mArtistId; d->mInsertArtistsQuery.finish(); Q_EMIT artistAdded(internalArtistFromId(d->mArtistId - 1)); return result; } void DatabaseInterface::insertTrackOrigin(const QUrl &fileNameURI, qulonglong discoverId) { d->mInsertTrackMapping.bindValue(QStringLiteral(":discoverId"), discoverId); d->mInsertTrackMapping.bindValue(QStringLiteral(":fileName"), fileNameURI); d->mInsertTrackMapping.bindValue(QStringLiteral(":priority"), 1); auto queryResult = d->mInsertTrackMapping.exec(); if (!queryResult || !d->mInsertTrackMapping.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertArtist" << d->mInsertTrackMapping.lastQuery(); qDebug() << "DatabaseInterface::insertArtist" << d->mInsertTrackMapping.boundValues(); qDebug() << "DatabaseInterface::insertArtist" << d->mInsertTrackMapping.lastError(); d->mInsertTrackMapping.finish(); return; } d->mInsertTrackMapping.finish(); } void DatabaseInterface::updateTrackOrigin(qulonglong trackId, const QUrl &fileName) { d->mUpdateTrackMapping.bindValue(QStringLiteral(":trackId"), trackId); d->mUpdateTrackMapping.bindValue(QStringLiteral(":fileName"), fileName); d->mUpdateTrackMapping.bindValue(QStringLiteral(":priority"), computeTrackPriority(trackId, fileName)); auto queryResult = d->mUpdateTrackMapping.exec(); if (!queryResult || !d->mUpdateTrackMapping.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::updateTrackOrigin" << d->mUpdateTrackMapping.lastQuery(); qDebug() << "DatabaseInterface::updateTrackOrigin" << d->mUpdateTrackMapping.boundValues(); qDebug() << "DatabaseInterface::updateTrackOrigin" << d->mUpdateTrackMapping.lastError(); d->mUpdateTrackMapping.finish(); return; } d->mInsertTrackMapping.finish(); } int DatabaseInterface::computeTrackPriority(qulonglong trackId, const QUrl &fileName) { auto result = int(1); if (!d) { return result; } d->mSelectTracksMappingPriority.bindValue(QStringLiteral(":trackId"), trackId); d->mSelectTracksMappingPriority.bindValue(QStringLiteral(":fileName"), fileName); auto queryResult = d->mSelectTracksMappingPriority.exec(); if (!queryResult || !d->mSelectTracksMappingPriority.isSelect() || !d->mSelectTracksMappingPriority.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::internalTrackIdFromFileName" << d->mSelectTracksMappingPriority.lastQuery(); qDebug() << "DatabaseInterface::internalTrackIdFromFileName" << d->mSelectTracksMappingPriority.boundValues(); qDebug() << "DatabaseInterface::internalTrackIdFromFileName" << d->mSelectTracksMappingPriority.lastError(); d->mSelectTracksMappingPriority.finish(); return result; } if (d->mSelectTracksMappingPriority.next()) { result = d->mSelectTracksMappingPriority.record().value(0).toInt(); d->mSelectTracksMappingPriority.finish(); return result; } d->mSelectTracksMappingPriority.finish(); d->mSelectTracksMappingPriorityByTrackId.bindValue(QStringLiteral(":trackId"), trackId); queryResult = d->mSelectTracksMappingPriorityByTrackId.exec(); if (!queryResult || !d->mSelectTracksMappingPriorityByTrackId.isSelect() || !d->mSelectTracksMappingPriorityByTrackId.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::internalTrackIdFromFileName" << d->mSelectTracksMappingPriorityByTrackId.lastQuery(); qDebug() << "DatabaseInterface::internalTrackIdFromFileName" << d->mSelectTracksMappingPriorityByTrackId.boundValues(); qDebug() << "DatabaseInterface::internalTrackIdFromFileName" << d->mSelectTracksMappingPriorityByTrackId.lastError(); d->mSelectTracksMappingPriorityByTrackId.finish(); return result; } if (d->mSelectTracksMappingPriorityByTrackId.next()) { result = d->mSelectTracksMappingPriorityByTrackId.record().value(0).toInt() + 1; } d->mSelectTracksMappingPriorityByTrackId.finish(); return result; } qulonglong DatabaseInterface::internalInsertTrack(const MusicAudioTrack &oneTrack, const QHash &covers, int originTrackId, QSet &modifiedAlbumIds, TrackFileInsertType insertType) { qulonglong resultId = 0; if (oneTrack.albumArtist().isEmpty()) { return resultId; } auto albumId = insertAlbum(oneTrack.albumName(), (oneTrack.isValidAlbumArtist() ? oneTrack.albumArtist() : QString()), oneTrack.artist(), covers[oneTrack.resourceURI().toString()], 0, true); if (albumId == 0) { return resultId; } auto otherTrackId = getDuplicateTrackIdFromTitleAlbumTracDiscNumber(oneTrack.title(), oneTrack.albumName(), oneTrack.albumArtist(), oneTrack.trackNumber(), oneTrack.discNumber()); bool isModifiedTrack = (otherTrackId != 0) || (insertType == TrackFileInsertType::ModifiedTrackFileInsert); bool isSameTrack = false; qulonglong oldAlbumId = 0; if (isModifiedTrack) { if (otherTrackId == 0) { otherTrackId = internalTrackIdFromFileName(oneTrack.resourceURI()); } originTrackId = otherTrackId; const auto &oldTrack = internalTrackFromDatabaseId(originTrackId); isSameTrack = (oldTrack.title() == oneTrack.title()); isSameTrack = isSameTrack && (oldTrack.albumName() == oneTrack.albumName()); isSameTrack = isSameTrack && (oldTrack.artist() == oneTrack.artist()); isSameTrack = isSameTrack && (oldTrack.trackNumber() == oneTrack.trackNumber()); isSameTrack = isSameTrack && (oldTrack.discNumber() == oneTrack.discNumber()); isSameTrack = isSameTrack && (oldTrack.duration() == oneTrack.duration()); isSameTrack = isSameTrack && (oldTrack.rating() == oneTrack.rating()); isSameTrack = isSameTrack && (oldTrack.resourceURI() == oneTrack.resourceURI()); isSameTrack = isSameTrack && (oldTrack.genre() == oneTrack.genre()); isSameTrack = isSameTrack && (oldTrack.composer() == oneTrack.composer()); isSameTrack = isSameTrack && (oldTrack.lyricist() == oneTrack.lyricist()); isSameTrack = isSameTrack && (oldTrack.comment() == oneTrack.comment()); isSameTrack = isSameTrack && (oldTrack.year() == oneTrack.year()); isSameTrack = isSameTrack && (oldTrack.channels() == oneTrack.channels()); isSameTrack = isSameTrack && (oldTrack.bitRate() == oneTrack.bitRate()); isSameTrack = isSameTrack && (oldTrack.sampleRate() == oneTrack.sampleRate()); oldAlbumId = internalAlbumIdFromTitleAndArtist(oldTrack.albumName(), oldTrack.albumArtist()); if (!isSameTrack) { removeTrackInDatabase(originTrackId); } } else { originTrackId = d->mTrackId; } resultId = originTrackId; if (!isSameTrack) { d->mInsertTrackQuery.bindValue(QStringLiteral(":trackId"), originTrackId); d->mInsertTrackQuery.bindValue(QStringLiteral(":title"), oneTrack.title()); d->mInsertTrackQuery.bindValue(QStringLiteral(":album"), albumId); d->mInsertTrackQuery.bindValue(QStringLiteral(":trackNumber"), oneTrack.trackNumber()); d->mInsertTrackQuery.bindValue(QStringLiteral(":discNumber"), oneTrack.discNumber()); d->mInsertTrackQuery.bindValue(QStringLiteral(":trackDuration"), QVariant::fromValue(oneTrack.duration().msecsSinceStartOfDay())); d->mInsertTrackQuery.bindValue(QStringLiteral(":trackRating"), oneTrack.rating()); d->mInsertTrackQuery.bindValue(QStringLiteral(":genre"), oneTrack.genre()); d->mInsertTrackQuery.bindValue(QStringLiteral(":composer"), oneTrack.composer()); d->mInsertTrackQuery.bindValue(QStringLiteral(":lyricist"), oneTrack.lyricist()); d->mInsertTrackQuery.bindValue(QStringLiteral(":comment"), oneTrack.comment()); d->mInsertTrackQuery.bindValue(QStringLiteral(":year"), oneTrack.year()); d->mInsertTrackQuery.bindValue(QStringLiteral(":channels"), oneTrack.channels()); d->mInsertTrackQuery.bindValue(QStringLiteral(":bitRate"), oneTrack.bitRate()); d->mInsertTrackQuery.bindValue(QStringLiteral(":sampleRate"), oneTrack.sampleRate()); auto result = d->mInsertTrackQuery.exec(); if (result && d->mInsertTrackQuery.isActive()) { d->mInsertTrackQuery.finish(); d->mInsertTrackArtistQuery.bindValue(QStringLiteral(":trackId"), originTrackId); d->mInsertTrackArtistQuery.bindValue(QStringLiteral(":artistId"), insertArtist(oneTrack.artist())); result = d->mInsertTrackArtistQuery.exec(); if (!result || !d->mInsertTrackArtistQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::internalInsertTrack" << d->mInsertTrackArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::internalInsertTrack" << d->mInsertTrackArtistQuery.boundValues(); qDebug() << "DatabaseInterface::internalInsertTrack" << d->mInsertTrackArtistQuery.lastError(); d->mInsertTrackArtistQuery.finish(); return resultId; } d->mInsertTrackArtistQuery.finish(); if (!isModifiedTrack) { ++d->mTrackId; } updateTrackOrigin(originTrackId, oneTrack.resourceURI()); if (isModifiedTrack) { Q_EMIT trackModified(internalTrackFromDatabaseId(originTrackId)); modifiedAlbumIds.insert(albumId); if (oldAlbumId != 0) { modifiedAlbumIds.insert(oldAlbumId); } } else { Q_EMIT trackAdded(originTrackId); } if (updateAlbumFromId(albumId, covers[oneTrack.resourceURI().toString()], oneTrack)) { modifiedAlbumIds.insert(albumId); } if (updateTracksCount(albumId)) { modifiedAlbumIds.insert(albumId); } } else { d->mInsertTrackQuery.finish(); Q_EMIT databaseError(); qDebug() << "DatabaseInterface::internalInsertTrack" << oneTrack << oneTrack.resourceURI(); qDebug() << "DatabaseInterface::internalInsertTrack" << d->mInsertTrackQuery.lastQuery(); qDebug() << "DatabaseInterface::internalInsertTrack" << d->mInsertTrackQuery.boundValues(); qDebug() << "DatabaseInterface::internalInsertTrack" << d->mInsertTrackQuery.lastError(); } } return resultId; } MusicAudioTrack DatabaseInterface::buildTrackFromDatabaseRecord(const QSqlRecord &trackRecord) const { auto result = MusicAudioTrack(); result.setDatabaseId(trackRecord.value(0).toULongLong()); result.setTitle(trackRecord.value(1).toString()); result.setParentId(trackRecord.value(2).toString()); result.setArtist(trackRecord.value(3).toString()); if (trackRecord.value(4).isValid()) { result.setAlbumArtist(trackRecord.value(4).toString()); } result.setResourceURI(trackRecord.value(5).toUrl()); result.setTrackNumber(trackRecord.value(6).toInt()); result.setDiscNumber(trackRecord.value(7).toInt()); result.setDuration(QTime::fromMSecsSinceStartOfDay(trackRecord.value(8).toInt())); result.setAlbumName(trackRecord.value(9).toString()); result.setRating(trackRecord.value(10).toInt()); result.setAlbumCover(trackRecord.value(11).toUrl()); result.setIsSingleDiscAlbum(trackRecord.value(12).toBool()); result.setGenre(trackRecord.value(13).toString()); result.setComposer(trackRecord.value(14).toString()); result.setLyricist(trackRecord.value(15).toString()); result.setComment(trackRecord.value(16).toString()); result.setYear(trackRecord.value(17).toInt()); result.setChannels(trackRecord.value(18).toInt()); result.setBitRate(trackRecord.value(19).toInt()); result.setSampleRate(trackRecord.value(20).toInt()); result.setValid(true); return result; } void DatabaseInterface::internalRemoveTracksList(const QList &removedTracks) { for (const auto &removedTrackFileName : removedTracks) { d->mRemoveTracksMapping.bindValue(QStringLiteral(":fileName"), removedTrackFileName.toString()); auto result = d->mRemoveTracksMapping.exec(); if (!result || !d->mRemoveTracksMapping.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::internalRemoveTracksList" << d->mRemoveTracksMapping.lastQuery(); qDebug() << "DatabaseInterface::internalRemoveTracksList" << d->mRemoveTracksMapping.boundValues(); qDebug() << "DatabaseInterface::internalRemoveTracksList" << d->mRemoveTracksMapping.lastError(); continue; } d->mRemoveTracksMapping.finish(); } internalRemoveTracksWithoutMapping(); } void DatabaseInterface::internalRemoveTracksList(const QList &removedTracks, qulonglong sourceId) { for (const auto &removedTrackFileName : removedTracks) { d->mRemoveTracksMappingFromSource.bindValue(QStringLiteral(":fileName"), removedTrackFileName.toString()); d->mRemoveTracksMappingFromSource.bindValue(QStringLiteral(":sourceId"), sourceId); auto result = d->mRemoveTracksMappingFromSource.exec(); if (!result || !d->mRemoveTracksMappingFromSource.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::removeTracksList" << d->mRemoveTracksMappingFromSource.lastQuery(); qDebug() << "DatabaseInterface::removeTracksList" << d->mRemoveTracksMappingFromSource.boundValues(); qDebug() << "DatabaseInterface::removeTracksList" << d->mRemoveTracksMappingFromSource.lastError(); continue; } d->mRemoveTracksMappingFromSource.finish(); } internalRemoveTracksWithoutMapping(); } void DatabaseInterface::internalRemoveTracksWithoutMapping() { auto queryResult = d->mSelectTracksWithoutMappingQuery.exec(); if (!queryResult || !d->mSelectTracksWithoutMappingQuery.isSelect() || !d->mSelectTracksWithoutMappingQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertArtist" << d->mSelectTracksWithoutMappingQuery.lastQuery(); qDebug() << "DatabaseInterface::insertArtist" << d->mSelectTracksWithoutMappingQuery.boundValues(); qDebug() << "DatabaseInterface::insertArtist" << d->mSelectTracksWithoutMappingQuery.lastError(); d->mSelectTracksWithoutMappingQuery.finish(); return; } QList willRemoveTrack; while (d->mSelectTracksWithoutMappingQuery.next()) { const auto ¤tRecord = d->mSelectTracksWithoutMappingQuery.record(); willRemoveTrack.push_back(buildTrackFromDatabaseRecord(currentRecord)); } d->mSelectTracksWithoutMappingQuery.finish(); QSet modifiedAlbums; for (const auto &oneRemovedTrack : willRemoveTrack) { removeTrackInDatabase(oneRemovedTrack.databaseId()); Q_EMIT trackRemoved(oneRemovedTrack.databaseId()); const auto &modifiedAlbumId = internalAlbumIdFromTitleAndArtist(oneRemovedTrack.albumName(), oneRemovedTrack.albumArtist()); const auto &allTracksFromArtist = internalTracksFromAuthor(oneRemovedTrack.artist()); const auto &allAlbumsFromArtist = internalAlbumIdsFromAuthor(oneRemovedTrack.artist()); const auto &removedArtistId = internalArtistIdFromName(oneRemovedTrack.artist()); const auto &removedArtist = internalArtistFromId(removedArtistId); if (updateTracksCount(modifiedAlbumId)) { modifiedAlbums.insert(modifiedAlbumId); } updateAlbumFromId(modifiedAlbumId, oneRemovedTrack.albumCover(), oneRemovedTrack); if (allTracksFromArtist.isEmpty() && allAlbumsFromArtist.isEmpty()) { removeArtistInDatabase(removedArtistId); Q_EMIT artistRemoved(removedArtist); } } for (auto modifiedAlbumId : modifiedAlbums) { auto modifiedAlbum = internalAlbumFromId(modifiedAlbumId); if (modifiedAlbum.isValid() && !modifiedAlbum.isEmpty()) { Q_EMIT albumModified(modifiedAlbum, modifiedAlbumId); } else { removeAlbumInDatabase(modifiedAlbum.databaseId()); Q_EMIT albumRemoved(modifiedAlbum, modifiedAlbumId); const auto &allTracksFromArtist = internalTracksFromAuthor(modifiedAlbum.artist()); const auto &allAlbumsFromArtist = internalAlbumIdsFromAuthor(modifiedAlbum.artist()); const auto &removedArtistId = internalArtistIdFromName(modifiedAlbum.artist()); const auto &removedArtist = internalArtistFromId(removedArtistId); if (allTracksFromArtist.isEmpty() && allAlbumsFromArtist.isEmpty()) { removeArtistInDatabase(removedArtistId); Q_EMIT artistRemoved(removedArtist); } } } } +QUrl DatabaseInterface::internalAlbumArtUriFromAlbumId(qulonglong albumId) +{ + auto result = QUrl(); + + d->mSelectAlbumArtUriFromAlbumIdQuery.bindValue(QStringLiteral(":albumId"), albumId); + + auto queryResult = d->mSelectAlbumArtUriFromAlbumIdQuery.exec(); + + if (!queryResult || !d->mSelectAlbumArtUriFromAlbumIdQuery.isSelect() || !d->mSelectAlbumArtUriFromAlbumIdQuery.isActive()) { + Q_EMIT databaseError(); + + qDebug() << "DatabaseInterface::insertArtist" << d->mSelectAlbumArtUriFromAlbumIdQuery.lastQuery(); + qDebug() << "DatabaseInterface::insertArtist" << d->mSelectAlbumArtUriFromAlbumIdQuery.boundValues(); + qDebug() << "DatabaseInterface::insertArtist" << d->mSelectAlbumArtUriFromAlbumIdQuery.lastError(); + + d->mSelectAlbumArtUriFromAlbumIdQuery.finish(); + + return result; + } + + if (!d->mSelectAlbumArtUriFromAlbumIdQuery.next()) { + d->mSelectAlbumArtUriFromAlbumIdQuery.finish(); + + return result; + } + + result = d->mSelectAlbumArtUriFromAlbumIdQuery.record().value(0).toUrl(); + + d->mSelectAlbumArtUriFromAlbumIdQuery.finish(); + + return result; +} + +bool DatabaseInterface::isValidArtist(qulonglong albumId) +{ + auto result = false; + + d->mSelectAlbumQuery.bindValue(QStringLiteral(":albumId"), albumId); + + auto queryResult = d->mSelectAlbumQuery.exec(); + + if (!queryResult || !d->mSelectAlbumQuery.isSelect() || !d->mSelectAlbumQuery.isActive()) { + Q_EMIT databaseError(); + + qDebug() << "DatabaseInterface::internalAlbumFromId" << d->mSelectAlbumQuery.lastQuery(); + qDebug() << "DatabaseInterface::internalAlbumFromId" << d->mSelectAlbumQuery.boundValues(); + qDebug() << "DatabaseInterface::internalAlbumFromId" << d->mSelectAlbumQuery.lastError(); + + d->mSelectAlbumQuery.finish(); + + return result; + } + + if (!d->mSelectAlbumQuery.next()) { + d->mSelectAlbumQuery.finish(); + + return result; + } + + const auto ¤tRecord = d->mSelectAlbumQuery.record(); + + result = !currentRecord.value(3).toString().isEmpty(); + + return result; +} + qulonglong DatabaseInterface::internalArtistIdFromName(const QString &name) { auto result = qulonglong(0); if (name.isEmpty()) { return result; } d->mSelectArtistByNameQuery.bindValue(QStringLiteral(":name"), name); auto queryResult = d->mSelectArtistByNameQuery.exec(); if (!queryResult || !d->mSelectArtistByNameQuery.isSelect() || !d->mSelectArtistByNameQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertArtist" << d->mSelectArtistByNameQuery.lastQuery(); qDebug() << "DatabaseInterface::insertArtist" << d->mSelectArtistByNameQuery.boundValues(); qDebug() << "DatabaseInterface::insertArtist" << d->mSelectArtistByNameQuery.lastError(); d->mSelectArtistByNameQuery.finish(); return result; } if (!d->mSelectArtistByNameQuery.next()) { d->mSelectArtistByNameQuery.finish(); return result; } result = d->mSelectArtistByNameQuery.record().value(0).toULongLong(); d->mSelectArtistByNameQuery.finish(); return result; } void DatabaseInterface::removeTrackInDatabase(qulonglong trackId) { d->mRemoveTrackArtistQuery.bindValue(QStringLiteral(":trackId"), trackId); auto result = d->mRemoveTrackArtistQuery.exec(); if (!result || !d->mRemoveTrackArtistQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::removeTrackInDatabase" << d->mRemoveTrackArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::removeTrackInDatabase" << d->mRemoveTrackArtistQuery.boundValues(); qDebug() << "DatabaseInterface::removeTrackInDatabase" << d->mRemoveTrackArtistQuery.lastError(); } d->mRemoveTrackArtistQuery.finish(); d->mRemoveTrackQuery.bindValue(QStringLiteral(":trackId"), trackId); result = d->mRemoveTrackQuery.exec(); if (!result || !d->mRemoveTrackQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::removeTrackInDatabase" << d->mRemoveTrackQuery.lastQuery(); qDebug() << "DatabaseInterface::removeTrackInDatabase" << d->mRemoveTrackQuery.boundValues(); qDebug() << "DatabaseInterface::removeTrackInDatabase" << d->mRemoveTrackQuery.lastError(); } d->mRemoveTrackQuery.finish(); } void DatabaseInterface::removeAlbumInDatabase(qulonglong albumId) { d->mRemoveAlbumArtistQuery.bindValue(QStringLiteral(":albumId"), albumId); auto result = d->mRemoveAlbumArtistQuery.exec(); if (!result || !d->mRemoveAlbumArtistQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::removeAlbumInDatabase" << d->mRemoveAlbumArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::removeAlbumInDatabase" << d->mRemoveAlbumArtistQuery.boundValues(); qDebug() << "DatabaseInterface::removeAlbumInDatabase" << d->mRemoveAlbumArtistQuery.lastError(); } d->mRemoveAlbumArtistQuery.finish(); d->mRemoveAlbumQuery.bindValue(QStringLiteral(":albumId"), albumId); result = d->mRemoveAlbumQuery.exec(); if (!result || !d->mRemoveAlbumQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::removeAlbumInDatabase" << d->mRemoveAlbumQuery.lastQuery(); qDebug() << "DatabaseInterface::removeAlbumInDatabase" << d->mRemoveAlbumQuery.boundValues(); qDebug() << "DatabaseInterface::removeAlbumInDatabase" << d->mRemoveAlbumQuery.lastError(); } d->mRemoveAlbumQuery.finish(); } void DatabaseInterface::removeArtistInDatabase(qulonglong artistId) { d->mRemoveArtistQuery.bindValue(QStringLiteral(":artistId"), artistId); auto result = d->mRemoveArtistQuery.exec(); if (!result || !d->mRemoveArtistQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::removeArtistInDatabase" << d->mRemoveArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::removeArtistInDatabase" << d->mRemoveArtistQuery.boundValues(); qDebug() << "DatabaseInterface::removeArtistInDatabase" << d->mRemoveArtistQuery.lastError(); } d->mRemoveArtistQuery.finish(); } void DatabaseInterface::reloadExistingDatabase() { auto transactionResult = startTransaction(); if (!transactionResult) { return; } d->mInitialUpdateTracksValidity.exec(); qDebug() << "DatabaseInterface::reloadExistingDatabase"; transactionResult = finishTransaction(); if (!transactionResult) { return; } const auto restoredArtists = allArtists(); for (const auto &oneArtist : restoredArtists) { d->mArtistId = std::max(d->mArtistId, oneArtist.databaseId()); Q_EMIT artistAdded(oneArtist); } ++d->mArtistId; const auto restoredAlbums = allAlbums(); for (const auto &oneAlbum : restoredAlbums) { d->mAlbumId = std::max(d->mAlbumId, oneAlbum.databaseId()); Q_EMIT albumAdded(oneAlbum); } ++d->mAlbumId; const auto restoredTracks = allTracks(); Q_EMIT tracksAdded(restoredTracks); for (const auto &oneTrack : restoredTracks) { d->mTrackId = std::max(d->mTrackId, oneTrack.databaseId()); Q_EMIT trackAdded(oneTrack.databaseId()); } ++d->mTrackId; } qulonglong DatabaseInterface::insertMusicSource(const QString &name) { qulonglong result = 0; d->mSelectMusicSource.bindValue(QStringLiteral(":name"), name); auto queryResult = d->mSelectMusicSource.exec(); if (!queryResult || !d->mSelectMusicSource.isSelect() || !d->mSelectMusicSource.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mSelectMusicSource.lastQuery(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mSelectMusicSource.boundValues(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mSelectMusicSource.lastError(); d->mSelectMusicSource.finish(); return result; } if (d->mSelectMusicSource.next()) { result = d->mSelectMusicSource.record().value(0).toULongLong(); d->mSelectMusicSource.finish(); return result; } d->mSelectMusicSource.finish(); d->mInsertMusicSource.bindValue(QStringLiteral(":discoverId"), d->mDiscoverId); d->mInsertMusicSource.bindValue(QStringLiteral(":name"), name); queryResult = d->mInsertMusicSource.exec(); if (!queryResult || !d->mInsertMusicSource.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mInsertMusicSource.lastQuery(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mInsertMusicSource.boundValues(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mInsertMusicSource.lastError(); d->mInsertMusicSource.finish(); return d->mDiscoverId; } d->mInsertMusicSource.finish(); ++d->mDiscoverId; return d->mDiscoverId - 1; } QList DatabaseInterface::fetchTracks(qulonglong albumId) { auto allTracks = QList(); d->mSelectTrackQuery.bindValue(QStringLiteral(":albumId"), albumId); auto result = d->mSelectTrackQuery.exec(); if (!result || !d->mSelectTrackQuery.isSelect() || !d->mSelectTrackQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::fetchTracks" << d->mSelectTrackQuery.lastQuery(); qDebug() << "DatabaseInterface::fetchTracks" << d->mSelectTrackQuery.boundValues(); qDebug() << "DatabaseInterface::fetchTracks" << d->mSelectTrackQuery.lastError(); } while (d->mSelectTrackQuery.next()) { const auto ¤tRecord = d->mSelectTrackQuery.record(); allTracks.push_back(buildTrackFromDatabaseRecord(currentRecord)); } d->mSelectTrackQuery.finish(); updateTracksCount(albumId); return allTracks; } bool DatabaseInterface::updateTracksCount(qulonglong albumId) { bool isModified = false; d->mSelectAlbumTrackCountQuery.bindValue(QStringLiteral(":albumId"), albumId); auto result = d->mSelectAlbumTrackCountQuery.exec(); if (!result || !d->mSelectAlbumTrackCountQuery.isSelect() || !d->mSelectAlbumTrackCountQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::updateTracksCount" << d->mSelectAlbumTrackCountQuery.lastQuery(); qDebug() << "DatabaseInterface::updateTracksCount" << d->mSelectAlbumTrackCountQuery.boundValues(); qDebug() << "DatabaseInterface::updateTracksCount" << d->mSelectAlbumTrackCountQuery.lastError(); d->mSelectAlbumTrackCountQuery.finish(); return isModified; } if (!d->mSelectAlbumTrackCountQuery.next()) { d->mSelectAlbumTrackCountQuery.finish(); return isModified; } auto oldTracksCount = d->mSelectAlbumTrackCountQuery.record().value(0).toInt(); d->mUpdateAlbumQuery.bindValue(QStringLiteral(":albumId"), albumId); result = d->mUpdateAlbumQuery.exec(); if (!result || !d->mUpdateAlbumQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::updateTracksCount" << d->mUpdateAlbumQuery.lastQuery(); qDebug() << "DatabaseInterface::updateTracksCount" << d->mUpdateAlbumQuery.boundValues(); qDebug() << "DatabaseInterface::updateTracksCount" << d->mUpdateAlbumQuery.lastError(); d->mUpdateAlbumQuery.finish(); return isModified; } d->mUpdateAlbumQuery.finish(); d->mSelectAlbumTrackCountQuery.bindValue(QStringLiteral(":albumId"), albumId); result = d->mSelectAlbumTrackCountQuery.exec(); if (!result || !d->mSelectAlbumTrackCountQuery.isSelect() || !d->mSelectAlbumTrackCountQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::updateTracksCount" << d->mSelectAlbumTrackCountQuery.lastQuery(); qDebug() << "DatabaseInterface::updateTracksCount" << d->mSelectAlbumTrackCountQuery.boundValues(); qDebug() << "DatabaseInterface::updateTracksCount" << d->mSelectAlbumTrackCountQuery.lastError(); d->mSelectAlbumTrackCountQuery.finish(); return isModified; } if (!d->mSelectAlbumTrackCountQuery.next()) { d->mSelectAlbumTrackCountQuery.finish(); return isModified; } auto newTracksCount = d->mSelectAlbumTrackCountQuery.record().value(0).toInt(); isModified = (newTracksCount != oldTracksCount); return isModified; } MusicAlbum DatabaseInterface::internalAlbumFromId(qulonglong albumId) { auto retrievedAlbum = MusicAlbum(); d->mSelectAlbumQuery.bindValue(QStringLiteral(":albumId"), albumId); auto result = d->mSelectAlbumQuery.exec(); if (!result || !d->mSelectAlbumQuery.isSelect() || !d->mSelectAlbumQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::internalAlbumFromId" << d->mSelectAlbumQuery.lastQuery(); qDebug() << "DatabaseInterface::internalAlbumFromId" << d->mSelectAlbumQuery.boundValues(); qDebug() << "DatabaseInterface::internalAlbumFromId" << d->mSelectAlbumQuery.lastError(); d->mSelectAlbumQuery.finish(); return retrievedAlbum; } if (!d->mSelectAlbumQuery.next()) { d->mSelectAlbumQuery.finish(); return retrievedAlbum; } const auto ¤tRecord = d->mSelectAlbumQuery.record(); retrievedAlbum.setDatabaseId(currentRecord.value(0).toULongLong()); retrievedAlbum.setTitle(currentRecord.value(1).toString()); retrievedAlbum.setId(currentRecord.value(2).toString()); retrievedAlbum.setArtist(currentRecord.value(3).toString()); retrievedAlbum.setAlbumArtURI(currentRecord.value(4).toUrl()); retrievedAlbum.setTracksCount(currentRecord.value(5).toInt()); retrievedAlbum.setIsSingleDiscAlbum(currentRecord.value(6).toBool()); retrievedAlbum.setTracks(fetchTracks(albumId)); retrievedAlbum.setValid(true); d->mSelectAlbumQuery.finish(); return retrievedAlbum; } MusicAlbum DatabaseInterface::internalAlbumFromTitleAndArtist(const QString &title, const QString &artist) { auto result = MusicAlbum(); auto albumId = internalAlbumIdFromTitleAndArtist(title, artist); if (albumId == 0) { return result; } result = internalAlbumFromId(albumId); return result; } qulonglong DatabaseInterface::internalAlbumIdFromTitleAndArtist(const QString &title, const QString &artist) { auto result = qulonglong(0); d->mSelectAlbumIdFromTitleQuery.bindValue(QStringLiteral(":title"), title); d->mSelectAlbumIdFromTitleQuery.bindValue(QStringLiteral(":artistName"), artist); auto queryResult = d->mSelectAlbumIdFromTitleQuery.exec(); if (!queryResult || !d->mSelectAlbumIdFromTitleQuery.isSelect() || !d->mSelectAlbumIdFromTitleQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::internalAlbumIdFromTitleAndArtist" << d->mSelectAlbumIdFromTitleQuery.lastQuery(); qDebug() << "DatabaseInterface::internalAlbumIdFromTitleAndArtist" << d->mSelectAlbumIdFromTitleQuery.boundValues(); qDebug() << "DatabaseInterface::internalAlbumIdFromTitleAndArtist" << d->mSelectAlbumIdFromTitleQuery.lastError(); d->mSelectAlbumIdFromTitleQuery.finish(); return result; } if (d->mSelectAlbumIdFromTitleQuery.next()) { result = d->mSelectAlbumIdFromTitleQuery.record().value(0).toULongLong(); } d->mSelectAlbumIdFromTitleQuery.finish(); if (result == 0) { d->mSelectAlbumIdFromTitleWithoutArtistQuery.bindValue(QStringLiteral(":title"), title); auto queryResult = d->mSelectAlbumIdFromTitleWithoutArtistQuery.exec(); if (!queryResult || !d->mSelectAlbumIdFromTitleWithoutArtistQuery.isSelect() || !d->mSelectAlbumIdFromTitleWithoutArtistQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::internalAlbumIdFromTitleAndArtist" << d->mSelectAlbumIdFromTitleWithoutArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::internalAlbumIdFromTitleAndArtist" << d->mSelectAlbumIdFromTitleWithoutArtistQuery.boundValues(); qDebug() << "DatabaseInterface::internalAlbumIdFromTitleAndArtist" << d->mSelectAlbumIdFromTitleWithoutArtistQuery.lastError(); d->mSelectAlbumIdFromTitleWithoutArtistQuery.finish(); return result; } if (d->mSelectAlbumIdFromTitleWithoutArtistQuery.next()) { result = d->mSelectAlbumIdFromTitleWithoutArtistQuery.record().value(0).toULongLong(); } d->mSelectAlbumIdFromTitleWithoutArtistQuery.finish(); } return result; } MusicAudioTrack DatabaseInterface::internalTrackFromDatabaseId(qulonglong id) { auto result = MusicAudioTrack(); if (!d || !d->mTracksDatabase.isValid() || !d->mInitFinished) { return result; } d->mSelectTrackFromIdQuery.bindValue(QStringLiteral(":trackId"), id); auto queryResult = d->mSelectTrackFromIdQuery.exec(); if (!queryResult || !d->mSelectTrackFromIdQuery.isSelect() || !d->mSelectTrackFromIdQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::internalTrackFromDatabaseId" << d->mSelectAlbumQuery.lastQuery(); qDebug() << "DatabaseInterface::internalTrackFromDatabaseId" << d->mSelectAlbumQuery.boundValues(); qDebug() << "DatabaseInterface::internalTrackFromDatabaseId" << d->mSelectAlbumQuery.lastError(); d->mSelectTrackFromIdQuery.finish(); return result; } if (!d->mSelectTrackFromIdQuery.next()) { d->mSelectTrackFromIdQuery.finish(); return result; } const auto ¤tRecord = d->mSelectTrackFromIdQuery.record(); result = buildTrackFromDatabaseRecord(currentRecord); d->mSelectTrackFromIdQuery.finish(); return result; } qulonglong DatabaseInterface::internalTrackIdFromTitleAlbumTracDiscNumber(const QString &title, const QString &artist, const QString &album, int trackNumber, int discNumber) { auto result = qulonglong(0); if (!d) { return result; } d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.bindValue(QStringLiteral(":title"), title); d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.bindValue(QStringLiteral(":artist"), artist); d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.bindValue(QStringLiteral(":album"), album); d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.bindValue(QStringLiteral(":trackNumber"), trackNumber); d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.bindValue(QStringLiteral(":discNumber"), discNumber); auto queryResult = d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.exec(); if (!queryResult || !d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.isSelect() || !d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::trackIdFromTitleAlbumArtist" << d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.lastQuery(); qDebug() << "DatabaseInterface::trackIdFromTitleAlbumArtist" << d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.boundValues(); qDebug() << "DatabaseInterface::trackIdFromTitleAlbumArtist" << d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.lastError(); d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.finish(); return result; } if (d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.next()) { result = d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.record().value(0).toInt(); } d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.finish(); return result; } qulonglong DatabaseInterface::getDuplicateTrackIdFromTitleAlbumTracDiscNumber(const QString &title, const QString &album, const QString &albumArtist, int trackNumber, int discNumber) { auto result = qulonglong(0); if (!d) { return result; } d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.bindValue(QStringLiteral(":title"), title); d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.bindValue(QStringLiteral(":album"), album); d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.bindValue(QStringLiteral(":albumArtist"), albumArtist); d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.bindValue(QStringLiteral(":trackNumber"), trackNumber); d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.bindValue(QStringLiteral(":discNumber"), discNumber); auto queryResult = d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.exec(); if (!queryResult || !d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.isSelect() || !d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::trackIdFromTitleAlbumArtist" << d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.lastQuery(); qDebug() << "DatabaseInterface::trackIdFromTitleAlbumArtist" << d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.boundValues(); qDebug() << "DatabaseInterface::trackIdFromTitleAlbumArtist" << d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.lastError(); d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.finish(); return result; } if (d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.next()) { result = d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.record().value(0).toInt(); } d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.finish(); return result; } qulonglong DatabaseInterface::internalTrackIdFromFileName(const QUrl &fileName) { auto result = qulonglong(0); if (!d) { return result; } d->mSelectTracksMapping.bindValue(QStringLiteral(":fileName"), fileName); auto queryResult = d->mSelectTracksMapping.exec(); if (!queryResult || !d->mSelectTracksMapping.isSelect() || !d->mSelectTracksMapping.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::internalTrackIdFromFileName" << d->mSelectTracksMapping.lastQuery(); qDebug() << "DatabaseInterface::internalTrackIdFromFileName" << d->mSelectTracksMapping.boundValues(); qDebug() << "DatabaseInterface::internalTrackIdFromFileName" << d->mSelectTracksMapping.lastError(); d->mSelectTracksMapping.finish(); return result; } if (d->mSelectTracksMapping.next()) { const auto ¤tRecordValue = d->mSelectTracksMapping.record().value(0); if (currentRecordValue.isValid()) { result = currentRecordValue.toInt(); } } d->mSelectTracksMapping.finish(); return result; } QList DatabaseInterface::internalTracksFromAuthor(const QString &artistName) { auto allTracks = QList(); d->mSelectTracksFromArtist.bindValue(QStringLiteral(":artistName"), artistName); auto result = d->mSelectTracksFromArtist.exec(); if (!result || !d->mSelectTracksFromArtist.isSelect() || !d->mSelectTracksFromArtist.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::tracksFromAuthor" << d->mSelectTracksFromArtist.lastQuery(); qDebug() << "DatabaseInterface::tracksFromAuthor" << d->mSelectTracksFromArtist.boundValues(); qDebug() << "DatabaseInterface::tracksFromAuthor" << d->mSelectTracksFromArtist.lastError(); return allTracks; } while (d->mSelectTracksFromArtist.next()) { const auto ¤tRecord = d->mSelectTracksFromArtist.record(); allTracks.push_back(buildTrackFromDatabaseRecord(currentRecord)); } d->mSelectTracksFromArtist.finish(); return allTracks; } QList DatabaseInterface::internalAlbumIdsFromAuthor(const QString &artistName) { auto allAlbumIds = QList(); d->mSelectAlbumIdsFromArtist.bindValue(QStringLiteral(":artistName"), artistName); auto result = d->mSelectAlbumIdsFromArtist.exec(); if (!result || !d->mSelectAlbumIdsFromArtist.isSelect() || !d->mSelectAlbumIdsFromArtist.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::tracksFromAuthor" << d->mSelectAlbumIdsFromArtist.lastQuery(); qDebug() << "DatabaseInterface::tracksFromAuthor" << d->mSelectAlbumIdsFromArtist.boundValues(); qDebug() << "DatabaseInterface::tracksFromAuthor" << d->mSelectAlbumIdsFromArtist.lastError(); return allAlbumIds; } while (d->mSelectAlbumIdsFromArtist.next()) { const auto ¤tRecord = d->mSelectAlbumIdsFromArtist.record(); allAlbumIds.push_back(currentRecord.value(0).toULongLong()); } d->mSelectAlbumIdsFromArtist.finish(); return allAlbumIds; } #include "moc_databaseinterface.cpp" diff --git a/src/databaseinterface.h b/src/databaseinterface.h index faa4a2c9..66c55cf8 100644 --- a/src/databaseinterface.h +++ b/src/databaseinterface.h @@ -1,204 +1,208 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef DATABASEINTERFACE_H #define DATABASEINTERFACE_H #include "musicalbum.h" #include "musicaudiotrack.h" #include "musicartist.h" #include #include #include #include #include #include #include class DatabaseInterfacePrivate; class QMutex; class QSqlRecord; class DatabaseInterface : public QObject { Q_OBJECT public: enum class AlbumData { Title, Artist, Image, TracksCount, Id, }; explicit DatabaseInterface(QObject *parent = nullptr); ~DatabaseInterface() override; Q_INVOKABLE void init(const QString &dbName, const QString &databaseFileName = {}); MusicAlbum albumFromTitleAndArtist(const QString &title, const QString &artist); QList allTracks(); QList allTracksFromSource(const QString &musicSource); QList allInvalidTracksFromSource(const QString &musicSource); QList allAlbums(); QList allArtists(); QList tracksFromAuthor(const QString &artistName); MusicAudioTrack trackFromDatabaseId(qulonglong id); qulonglong trackIdFromTitleAlbumTrackDiscNumber(const QString &title, const QString &artist, const QString &album, int trackNumber, int discNumber); qulonglong trackIdFromFileName(const QUrl &fileName); void applicationAboutToQuit(); Q_SIGNALS: void artistAdded(const MusicArtist &newArtist); void albumAdded(const MusicAlbum &newAlbum); void trackAdded(qulonglong id); void tracksAdded(const QList &allTracks); void artistRemoved(const MusicArtist &removedArtist); void albumRemoved(const MusicAlbum &removedAlbum, qulonglong removedAlbumId); void trackRemoved(qulonglong id); void artistModified(const MusicArtist &modifiedArtist); void albumModified(const MusicAlbum &modifiedAlbum, qulonglong modifiedAlbumId); void trackModified(const MusicAudioTrack &modifiedTrack); void requestsInitDone(); void databaseError(); public Q_SLOTS: void insertTracksList(const QList &tracks, const QHash &covers, const QString &musicSource); void removeTracksList(const QList &removedTracks); void modifyTracksList(const QList &modifiedTracks, const QHash &covers, const QString &musicSource); void removeAllTracksFromSource(const QString &sourceName); void cleanInvalidTracks(); private: enum class TrackFileInsertType { NewTrackFileInsert, ModifiedTrackFileInsert, }; bool startTransaction() const; bool finishTransaction() const; bool rollBackTransaction() const; QList fetchTracks(qulonglong albumId); bool updateTracksCount(qulonglong albumId); MusicArtist internalArtistFromId(qulonglong artistId); MusicAlbum internalAlbumFromId(qulonglong albumId); MusicAlbum internalAlbumFromTitleAndArtist(const QString &title, const QString &artist); qulonglong internalAlbumIdFromTitleAndArtist(const QString &title, const QString &artist); MusicAudioTrack internalTrackFromDatabaseId(qulonglong id); qulonglong internalTrackIdFromTitleAlbumTracDiscNumber(const QString &title, const QString &artist, const QString &album, int trackNumber, int discNumber); qulonglong getDuplicateTrackIdFromTitleAlbumTracDiscNumber(const QString &title, const QString &album, const QString &albumArtist, int trackNumber, int discNumber); qulonglong internalTrackIdFromFileName(const QUrl &fileName); QList internalTracksFromAuthor(const QString &artistName); QList internalAlbumIdsFromAuthor(const QString &artistName); void initDatabase() const; void initRequest(); qulonglong insertAlbum(const QString &title, const QString &albumArtist, const QString &trackArtist, const QUrl &albumArtURI, int tracksCount, bool isSingleDiscAlbum); bool updateAlbumFromId(qulonglong albumId, const QUrl &albumArtUri, const MusicAudioTrack ¤tTrack); qulonglong insertArtist(const QString &name); qulonglong internalArtistIdFromName(const QString &name); void removeTrackInDatabase(qulonglong trackId); void removeAlbumInDatabase(qulonglong albumId); void removeArtistInDatabase(qulonglong artistId); void reloadExistingDatabase(); qulonglong insertMusicSource(const QString &name); void insertTrackOrigin(const QUrl &fileNameURI, qulonglong discoverId); void updateTrackOrigin(qulonglong trackId, const QUrl &fileName); int computeTrackPriority(qulonglong trackId, const QUrl &fileName); qulonglong internalInsertTrack(const MusicAudioTrack &oneModifiedTrack, const QHash &covers, int originTrackId, QSet &modifiedAlbumIds, TrackFileInsertType insertType); MusicAudioTrack buildTrackFromDatabaseRecord(const QSqlRecord &trackRecord) const; void internalRemoveTracksList(const QList &removedTracks); void internalRemoveTracksList(const QList &removedTracks, qulonglong sourceId); void internalRemoveTracksWithoutMapping(); + QUrl internalAlbumArtUriFromAlbumId(qulonglong albumId); + + bool isValidArtist(qulonglong albumId); + std::unique_ptr d; }; #endif // DATABASEINTERFACE_H diff --git a/src/file/localfilelisting.cpp b/src/file/localfilelisting.cpp index 4f2e2fa9..4e4f16fe 100644 --- a/src/file/localfilelisting.cpp +++ b/src/file/localfilelisting.cpp @@ -1,90 +1,90 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "localfilelisting.h" #include "musicaudiotrack.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include class LocalFileListingPrivate { public: QString mRootPath; }; LocalFileListing::LocalFileListing(QObject *parent) : AbstractFileListing(QStringLiteral("local"), parent), d(std::make_unique()) { } LocalFileListing::~LocalFileListing() = default; QString LocalFileListing::rootPath() const { return d->mRootPath; } void LocalFileListing::setRootPath(const QString &rootPath) { if (d->mRootPath == rootPath) { return; } d->mRootPath = rootPath; Q_EMIT rootPathChanged(); setSourceName(rootPath); } void LocalFileListing::executeInit() { } void LocalFileListing::triggerRefreshOfContent() { Q_EMIT indexingStarted(); AbstractFileListing::triggerRefreshOfContent(); scanDirectoryTree(d->mRootPath); - Q_EMIT indexingFinished(); + Q_EMIT indexingFinished(importedTracksCount()); } #include "moc_localfilelisting.cpp" diff --git a/src/main.cpp b/src/main.cpp index a9639981..059e9584 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,249 +1,253 @@ /* * Copyright 2015-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config-upnp-qt.h" //#define QT_QML_DEBUG #if defined UPNPQT_FOUND && UPNPQT_FOUND #include "upnp/upnpcontrolconnectionmanager.h" #include "upnp/upnpcontrolmediaserver.h" #include "upnp/upnpcontrolcontentdirectory.h" #include "upnp/upnpcontentdirectorymodel.h" #include "upnpdevicedescription.h" #include "upnp/didlparser.h" #include "upnp/upnpdiscoverallmusic.h" #include "upnpssdpengine.h" #include "upnpabstractservice.h" #include "upnpcontrolabstractdevice.h" #include "upnpcontrolabstractservice.h" #include "upnpbasictypes.h" #endif #include "progressindicator.h" #include "mediaplaylist.h" #include "managemediaplayercontrol.h" #include "manageheaderbar.h" #include "manageaudioplayer.h" #include "musicstatistics.h" #include "allalbumsmodel.h" #include "albummodel.h" #include "allartistsmodel.h" #include "musicaudiotrack.h" #include "musiclistenersmanager.h" #include "albumfilterproxymodel.h" #include "elisaapplication.h" #include "audiowrapper.h" #include "alltracksmodel.h" #include "notificationitem.h" #include "topnotificationmanager.h" #include "elisa_settings.h" #if defined Qt5DBus_FOUND && Qt5DBus_FOUND #include "mpris2/mpris2.h" #include "mpris2/mediaplayer2player.h" #endif #if defined KF5Declarative_FOUND && KF5Declarative_FOUND #include #endif #include #include #include #if defined KF5Crash_FOUND && KF5Crash_FOUND #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #if defined Qt5AndroidExtras_FOUND && Qt5AndroidExtras_FOUND #include #include #endif #if defined KF5DBusAddons_FOUND && KF5DBusAddons_FOUND #include #endif #if defined Q_OS_ANDROID int __attribute__((visibility("default"))) main(int argc, char *argv[]) #else int main(int argc, char *argv[]) #endif { qputenv("QT_GSTREAMER_USE_PLAYBIN_VOLUME", "true"); QApplication app(argc, argv); #if defined KF5Crash_FOUND && KF5Crash_FOUND KCrash::initialize(); #endif QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("new-audio-alarm"))); KLocalizedString::setApplicationDomain("elisa"); #if defined UPNPQT_FOUND && UPNPQT_FOUND qmlRegisterType("org.kde.elisa", 1, 0, "UpnpSsdpEngine"); qmlRegisterType("org.kde.elisa", 1, 0, "UpnpDiscoverAllMusic"); qmlRegisterType("org.kde.elisa", 1, 0, "UpnpAbstractDevice"); qmlRegisterType("org.kde.elisa", 1, 0, "UpnpAbstractService"); qmlRegisterType("org.kde.elisa", 1, 0, "UpnpControlAbstractDevice"); qmlRegisterType("org.kde.elisa", 1, 0, "UpnpControlAbstractService"); qmlRegisterType("org.kde.elisa", 1, 0, "UpnpControlConnectionManager"); qmlRegisterType("org.kde.elisa", 1, 0, "UpnpControlMediaServer"); qmlRegisterType("org.kde.elisa", 1, 0, "UpnpContentDirectoryModel"); qmlRegisterType("org.kde.elisa", 1, 0, "DidlParser"); qmlRegisterType("org.kde.elisa", 1, 0, "UpnpControlContentDirectory"); qmlRegisterType("org.kde.elisa", 1, 0, "UpnpDeviceDescription"); qRegisterMetaType(); qRegisterMetaType >(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); #endif qmlRegisterType("org.kde.elisa", 1, 0, "MediaPlayList"); qmlRegisterType("org.kde.elisa", 1, 0, "ManageMediaPlayerControl"); qmlRegisterType("org.kde.elisa", 1, 0, "ManageHeaderBar"); qmlRegisterType("org.kde.elisa", 1, 0, "ManageAudioPlayer"); qmlRegisterType("org.kde.elisa", 1, 0, "MusicStatistics"); qmlRegisterType("org.kde.elisa", 1, 0, "ProgressIndicator"); qmlRegisterType("org.kde.elisa", 1, 0, "AllAlbumsModel"); qmlRegisterType("org.kde.elisa", 1, 0, "AllArtistsModel"); qmlRegisterType("org.kde.elisa", 1, 0, "AlbumModel"); qmlRegisterType("org.kde.elisa", 1, 0, "AllTracksModel"); qmlRegisterType("org.kde.elisa", 1, 0, "MusicListenersManager"); qmlRegisterType("org.kde.elisa", 1, 0, "SortFilterProxyModel"); qmlRegisterType("org.kde.elisa", 1, 0, "AlbumFilterProxyModel"); qmlRegisterType("org.kde.elisa", 1, 0, "AudioWrapper"); qmlRegisterType("org.kde.elisa", 1, 0, "TopNotificationManager"); #if defined Qt5DBus_FOUND && Qt5DBus_FOUND qmlRegisterType("org.kde.elisa", 1, 0, "Mpris2"); qRegisterMetaType(); #endif qRegisterMetaType(); qRegisterMetaType>("QHash"); qRegisterMetaType>("QList"); qRegisterMetaType>("QVector"); qRegisterMetaType>("QVector"); qRegisterMetaType>("QHash"); qRegisterMetaType("MusicAlbum"); qRegisterMetaType("MusicArtist"); qRegisterMetaType>(); qRegisterMetaType(); qRegisterMetaType("NotificationItem"); qRegisterMetaType>("QMap"); qmlRegisterUncreatableType("org.kde.elisa", 1, 0, "ElisaApplication", QStringLiteral("only one and done in c++")); qRegisterMetaTypeStreamOperators("PlayListControler::PlayerState"); KAboutData aboutData( QStringLiteral("elisa"), i18n("Elisa"), QStringLiteral("0.0.81"), i18n("A Simple Music Player written with KDE Frameworks"), KAboutLicense::LGPL_V3, i18n("(c) 2015-2017, Matthieu Gallien <mgallien@mgallien.fr>")); aboutData.addAuthor(QStringLiteral("Matthieu Gallien"),i18n("Creator"), QStringLiteral("mgallien@mgallien.fr")); aboutData.addAuthor(QStringLiteral("Alexander Stippich"), i18n("Author"), QStringLiteral("a.stippich@gmx.net")); aboutData.addCredit(QStringLiteral("Andrew Lake"), i18n("Concept and design work"), QStringLiteral("jamboarder@gmail.com")); aboutData.addCredit(QStringLiteral("Luigi Toscano"), i18n("Localization support"), QStringLiteral("luigi.toscano@tiscali.it")); aboutData.addCredit(QStringLiteral("Safa Alfulaij"), i18n("Right to left support in interface"), QStringLiteral("safa1996alfulaij@gmail.com")); aboutData.addCredit(QStringLiteral("Diego Gangl"), i18n("Various improvements to the interface"), QStringLiteral("diego@sinestesia.co")); KAboutData::setApplicationData(aboutData); #if defined KF5DBusAddons_FOUND && KF5DBusAddons_FOUND KDBusService elisaService(KDBusService::Unique); #endif KLocalizedString::setApplicationDomain("elisa"); ElisaApplication myApp; #if defined KF5DBusAddons_FOUND && KF5DBusAddons_FOUND QObject::connect(&elisaService, &KDBusService::activateActionRequested, &myApp, &ElisaApplication::activateActionRequested); QObject::connect(&elisaService, &KDBusService::activateRequested, &myApp, &ElisaApplication::activateRequested); QObject::connect(&elisaService, &KDBusService::openRequested, &myApp, &ElisaApplication::openRequested); #endif QCommandLineParser parser; parser.addHelpOption(); parser.addVersionOption(); aboutData.setupCommandLine(&parser); parser.process(app); aboutData.processCommandLine(&parser); myApp.setArguments(parser.positionalArguments()); #if defined Qt5AndroidExtras_FOUND && Qt5AndroidExtras_FOUND qDebug() << QCoreApplication::arguments(); QAndroidJniObject::callStaticMethod("com/kde/elisa/ElisaService", "startMyService", "(Landroid/content/Context;)V", QtAndroid::androidContext().object()); #endif auto configurationFileName = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); configurationFileName += QStringLiteral("/elisarc"); Elisa::ElisaConfiguration::instance(configurationFileName); Elisa::ElisaConfiguration::self()->load(); Elisa::ElisaConfiguration::self()->save(); + MusicListenersManager myMusicManager; + myMusicManager.setElisaApplication(&myApp); + QQmlApplicationEngine engine; engine.addImportPath(QStringLiteral("qrc:/imports")); QQmlFileSelector selector(&engine); #if defined KF5Declarative_FOUND && KF5Declarative_FOUND KDeclarative::KDeclarative decl; decl.setDeclarativeEngine(&engine); decl.setupBindings(); #endif engine.rootContext()->setContextObject(new KLocalizedContext(&engine)); engine.rootContext()->setContextProperty(QStringLiteral("elisa"), &myApp); + engine.rootContext()->setContextProperty(QStringLiteral("allListeners"), &myMusicManager); engine.load(QUrl(QStringLiteral("qrc:/qml/ElisaMainWindow.qml"))); return app.exec(); } diff --git a/src/musiclistenersmanager.cpp b/src/musiclistenersmanager.cpp index e203c389..5be0b585 100644 --- a/src/musiclistenersmanager.cpp +++ b/src/musiclistenersmanager.cpp @@ -1,410 +1,477 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "musiclistenersmanager.h" #include "config-upnp-qt.h" #if defined UPNPQT_FOUND && UPNPQT_FOUND #include "upnp/upnplistener.h" #endif #if defined KF5Baloo_FOUND && KF5Baloo_FOUND #include "baloo/baloolistener.h" #endif #include "databaseinterface.h" #include "mediaplaylist.h" #include "file/filelistener.h" #include "file/localfilelisting.h" #include "trackslistener.h" #include "notificationitem.h" #include "elisaapplication.h" #include "elisa_settings.h" +#include "allalbumsmodel.h" +#include "allartistsmodel.h" +#include "alltracksmodel.h" #include #include #include #include #include #include #include #include #include #include #include #include class MusicListenersManagerPrivate { public: QThread mDatabaseThread; QThread mListenerThread; #if defined UPNPQT_FOUND && UPNPQT_FOUND UpnpListener mUpnpListener; #endif #if defined KF5Baloo_FOUND && KF5Baloo_FOUND QScopedPointer mBalooListener; #endif std::list> mFileListener; DatabaseInterface mDatabaseInterface; QFileSystemWatcher mConfigFileWatcher; int mImportedTracksCount = 0; + int mTotalImportedTracksCount = 0; + int mActiveMusicListenersCount = 0; bool mIndexingRunning = false; ElisaApplication *mElisaApplication = nullptr; + AllAlbumsModel mAllAlbumsModel; + + AllArtistsModel mAllArtistsModel; + + AllTracksModel mAllTracksModel; + + bool mIndexerBusy = false; + }; MusicListenersManager::MusicListenersManager(QObject *parent) : QObject(parent), d(std::make_unique()) { d->mListenerThread.start(); d->mDatabaseThread.start(); d->mDatabaseInterface.moveToThread(&d->mDatabaseThread); connect(&d->mDatabaseInterface, &DatabaseInterface::requestsInitDone, this, &MusicListenersManager::databaseReady); const auto &localDataPaths = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); auto databaseFileName = QString(); if (!localDataPaths.isEmpty()) { QDir myDataDirectory; myDataDirectory.mkpath(localDataPaths.first()); databaseFileName = localDataPaths.first() + QStringLiteral("/elisaDatabase.db"); } QMetaObject::invokeMethod(&d->mDatabaseInterface, "init", Qt::QueuedConnection, Q_ARG(QString, QStringLiteral("listeners")), Q_ARG(QString, databaseFileName)); connect(&d->mDatabaseInterface, &DatabaseInterface::artistAdded, this, &MusicListenersManager::artistAdded); connect(&d->mDatabaseInterface, &DatabaseInterface::albumAdded, this, &MusicListenersManager::albumAdded); connect(&d->mDatabaseInterface, &DatabaseInterface::trackAdded, this, &MusicListenersManager::trackAdded); connect(&d->mDatabaseInterface, &DatabaseInterface::tracksAdded, this, &MusicListenersManager::tracksAdded); connect(&d->mDatabaseInterface, &DatabaseInterface::artistRemoved, this, &MusicListenersManager::artistRemoved); connect(&d->mDatabaseInterface, &DatabaseInterface::albumRemoved, this, &MusicListenersManager::albumRemoved); connect(&d->mDatabaseInterface, &DatabaseInterface::trackRemoved, this, &MusicListenersManager::trackRemoved); connect(&d->mDatabaseInterface, &DatabaseInterface::artistModified, this, &MusicListenersManager::artistModified); connect(&d->mDatabaseInterface, &DatabaseInterface::albumModified, this, &MusicListenersManager::albumModified); connect(&d->mDatabaseInterface, &DatabaseInterface::trackModified, this, &MusicListenersManager::trackModified); connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &MusicListenersManager::applicationAboutToQuit); connect(Elisa::ElisaConfiguration::self(), &Elisa::ElisaConfiguration::configChanged, this, &MusicListenersManager::configChanged); connect(&d->mConfigFileWatcher, &QFileSystemWatcher::fileChanged, this, &MusicListenersManager::configChanged); auto initialRootPath = Elisa::ElisaConfiguration::rootPath(); if (initialRootPath.isEmpty()) { for (const auto &musicPath : QStandardPaths::standardLocations(QStandardPaths::MusicLocation)) { initialRootPath.push_back(musicPath); } Elisa::ElisaConfiguration::setRootPath(initialRootPath); Elisa::ElisaConfiguration::self()->save(); } d->mConfigFileWatcher.addPath(Elisa::ElisaConfiguration::self()->config()->name()); + + d->mAllAlbumsModel.setAllArtists(&d->mAllArtistsModel); + d->mAllArtistsModel.setAllAlbums(&d->mAllAlbumsModel); + + connect(&d->mDatabaseInterface, &DatabaseInterface::albumAdded, + &d->mAllAlbumsModel, &AllAlbumsModel::albumAdded); + connect(&d->mDatabaseInterface, &DatabaseInterface::albumModified, + &d->mAllAlbumsModel, &AllAlbumsModel::albumModified); + connect(&d->mDatabaseInterface, &DatabaseInterface::albumRemoved, + &d->mAllAlbumsModel, &AllAlbumsModel::albumRemoved); + + connect(&d->mDatabaseInterface, &DatabaseInterface::artistAdded, + &d->mAllArtistsModel, &AllArtistsModel::artistAdded); + connect(&d->mDatabaseInterface, &DatabaseInterface::artistModified, + &d->mAllArtistsModel, &AllArtistsModel::artistModified); + connect(&d->mDatabaseInterface, &DatabaseInterface::artistRemoved, + &d->mAllArtistsModel, &AllArtistsModel::artistRemoved); + + connect(&d->mDatabaseInterface, &DatabaseInterface::tracksAdded, + &d->mAllTracksModel, &AllTracksModel::tracksAdded); + connect(&d->mDatabaseInterface, &DatabaseInterface::trackModified, + &d->mAllTracksModel, &AllTracksModel::trackModified); + connect(&d->mDatabaseInterface, &DatabaseInterface::trackRemoved, + &d->mAllTracksModel, &AllTracksModel::trackRemoved); } MusicListenersManager::~MusicListenersManager() = default; DatabaseInterface *MusicListenersManager::viewDatabase() const { return &d->mDatabaseInterface; } void MusicListenersManager::subscribeForTracks(MediaPlayList *client) { auto helper = new TracksListener(&d->mDatabaseInterface); helper->moveToThread(&d->mDatabaseThread); connect(this, &MusicListenersManager::trackRemoved, helper, &TracksListener::trackRemoved); connect(this, &MusicListenersManager::tracksAdded, helper, &TracksListener::tracksAdded); connect(this, &MusicListenersManager::trackModified, helper, &TracksListener::trackModified); connect(this, &MusicListenersManager::removeTracksInError, &d->mDatabaseInterface, &DatabaseInterface::removeTracksList); connect(helper, &TracksListener::trackHasChanged, client, &MediaPlayList::trackChanged); connect(helper, &TracksListener::trackHasBeenRemoved, client, &MediaPlayList::trackRemoved); connect(helper, &TracksListener::albumAdded, client, &MediaPlayList::albumAdded); connect(client, &MediaPlayList::newTrackByIdInList, helper, &TracksListener::trackByIdInList); connect(client, &MediaPlayList::newTrackByNameInList, helper, &TracksListener::trackByNameInList); connect(client, &MediaPlayList::newTrackByFileNameInList, helper, &TracksListener::trackByFileNameInList); connect(client, &MediaPlayList::newArtistInList, helper, &TracksListener::newArtistInList); } int MusicListenersManager::importedTracksCount() const { return d->mImportedTracksCount; } bool MusicListenersManager::isIndexingRunning() const { return d->mIndexingRunning; } ElisaApplication *MusicListenersManager::elisaApplication() const { return d->mElisaApplication; } +QAbstractItemModel *MusicListenersManager::allAlbumsModel() const +{ + return &d->mAllAlbumsModel; +} + +QAbstractItemModel *MusicListenersManager::allArtistsModel() const +{ + return &d->mAllArtistsModel; +} + +QAbstractItemModel *MusicListenersManager::allTracksModel() const +{ + return &d->mAllTracksModel; +} + +bool MusicListenersManager::indexerBusy() const +{ + return d->mIndexerBusy; +} + void MusicListenersManager::databaseReady() { + d->mIndexerBusy = true; + Q_EMIT indexerBusyChanged(); + configChanged(); } void MusicListenersManager::applicationAboutToQuit() { d->mDatabaseInterface.applicationAboutToQuit(); Q_EMIT applicationIsTerminating(); d->mDatabaseThread.exit(); d->mDatabaseThread.wait(); d->mListenerThread.exit(); d->mListenerThread.wait(); } void MusicListenersManager::showConfiguration() { auto configureAction = d->mElisaApplication->action(QStringLiteral("options_configure")); configureAction->trigger(); } void MusicListenersManager::resetImportedTracksCounter() { #if defined KF5Baloo_FOUND && KF5Baloo_FOUND if (d->mBalooListener) { d->mBalooListener->resetImportedTracksCounter(); } #endif for (const auto &itFileListener : d->mFileListener) { itFileListener->resetImportedTracksCounter(); } } void MusicListenersManager::setElisaApplication(ElisaApplication *elisaApplication) { if (d->mElisaApplication == elisaApplication) { return; } d->mElisaApplication = elisaApplication; emit elisaApplicationChanged(); } void MusicListenersManager::playBackError(QUrl sourceInError, QMediaPlayer::Error playerError) { qDebug() << "MusicListenersManager::playBackError" << sourceInError; if (playerError == QMediaPlayer::ResourceError) { Q_EMIT removeTracksInError({sourceInError}); if (sourceInError.isLocalFile()) { Q_EMIT displayTrackError(sourceInError.toLocalFile()); } else { Q_EMIT displayTrackError(sourceInError.toString()); } } } void MusicListenersManager::configChanged() { auto currentConfiguration = Elisa::ElisaConfiguration::self(); d->mConfigFileWatcher.addPath(currentConfiguration->config()->name()); currentConfiguration->load(); #if defined KF5Baloo_FOUND && KF5Baloo_FOUND if (currentConfiguration->balooIndexer() && !d->mBalooListener) { d->mBalooListener.reset(new BalooListener); d->mBalooListener->moveToThread(&d->mListenerThread); d->mBalooListener->setDatabaseInterface(&d->mDatabaseInterface); connect(this, &MusicListenersManager::applicationIsTerminating, d->mBalooListener.data(), &BalooListener::applicationAboutToQuit, Qt::DirectConnection); connect(d->mBalooListener.data(), &BalooListener::indexingStarted, this, &MusicListenersManager::monitorStartingListeners); connect(d->mBalooListener.data(), &BalooListener::indexingFinished, this, &MusicListenersManager::monitorEndingListeners); connect(d->mBalooListener.data(), &BalooListener::clearDatabase, &d->mDatabaseInterface, &DatabaseInterface::removeAllTracksFromSource); connect(d->mBalooListener.data(), &BalooListener::importedTracksCountChanged, this, &MusicListenersManager::computeImportedTracksCount); connect(d->mBalooListener.data(), &BalooListener::newNotification, this, &MusicListenersManager::newNotification); connect(d->mBalooListener.data(), &BalooListener::closeNotification, this, &MusicListenersManager::closeNotification); QMetaObject::invokeMethod(d->mBalooListener.data(), "performInitialScan", Qt::QueuedConnection); } else if (!currentConfiguration->balooIndexer() && d->mBalooListener) { QMetaObject::invokeMethod(d->mBalooListener.data(), "quitListener", Qt::QueuedConnection); d->mBalooListener.reset(); } #endif #if defined UPNPQT_FOUND && UPNPQT_FOUND d->mUpnpListener.setDatabaseInterface(&d->mDatabaseInterface); d->mUpnpListener.moveToThread(&d->mDatabaseThread); connect(this, &MusicListenersManager::applicationIsTerminating, &d->mUpnpListener, &UpnpListener::applicationAboutToQuit, Qt::DirectConnection); #endif if (currentConfiguration->elisaFilesIndexer()) { const auto &allRootPaths = currentConfiguration->rootPath(); for (auto itFileListener = d->mFileListener.begin(); itFileListener != d->mFileListener.end(); ) { const auto ¤tRootPath = (*itFileListener)->localFileIndexer().rootPath(); auto itPath = std::find(allRootPaths.begin(), allRootPaths.end(), currentRootPath); if (itPath == allRootPaths.end()) { d->mDatabaseInterface.removeAllTracksFromSource((*itFileListener)->fileListing()->sourceName()); itFileListener = d->mFileListener.erase(itFileListener); } else { ++itFileListener; } } for (const auto &oneRootPath : allRootPaths) { auto itPath = std::find_if(d->mFileListener.begin(), d->mFileListener.end(), [&oneRootPath](const auto &value)->bool {return value->localFileIndexer().rootPath() == oneRootPath;}); if (itPath == d->mFileListener.end()) { auto newFileIndexer = std::make_unique(); newFileIndexer->setDatabaseInterface(&d->mDatabaseInterface); newFileIndexer->moveToThread(&d->mListenerThread); connect(this, &MusicListenersManager::applicationIsTerminating, newFileIndexer.get(), &FileListener::applicationAboutToQuit, Qt::DirectConnection); connect(newFileIndexer.get(), &FileListener::indexingStarted, this, &MusicListenersManager::monitorStartingListeners); connect(newFileIndexer.get(), &FileListener::indexingFinished, this, &MusicListenersManager::monitorEndingListeners); connect(newFileIndexer.get(), &FileListener::importedTracksCountChanged, this, &MusicListenersManager::computeImportedTracksCount); connect(newFileIndexer.get(), &FileListener::newNotification, this, &MusicListenersManager::newNotification); connect(newFileIndexer.get(), &FileListener::closeNotification, this, &MusicListenersManager::closeNotification); newFileIndexer->setRootPath(oneRootPath); QMetaObject::invokeMethod(newFileIndexer.get(), "performInitialScan", Qt::QueuedConnection); d->mFileListener.emplace_back(std::move(newFileIndexer)); } } } } void MusicListenersManager::computeImportedTracksCount() { #if defined KF5Baloo_FOUND && KF5Baloo_FOUND if (d->mBalooListener) { d->mImportedTracksCount = d->mBalooListener->importedTracksCount(); } else { d->mImportedTracksCount = 0; } #else d->mImportedTracksCount = 0; #endif for (const auto &itFileListener : d->mFileListener) { d->mImportedTracksCount += itFileListener->importedTracksCount(); } + if (d->mImportedTracksCount && d->mIndexerBusy) { + d->mIndexerBusy = false; + Q_EMIT indexerBusyChanged(); + } + if (d->mImportedTracksCount >= 4) { Q_EMIT closeNotification(QStringLiteral("notEnoughTracks")); } Q_EMIT importedTracksCountChanged(); } void MusicListenersManager::monitorStartingListeners() { if (d->mActiveMusicListenersCount == 0) { d->mIndexingRunning = true; Q_EMIT indexingRunningChanged(); } ++d->mActiveMusicListenersCount; } -void MusicListenersManager::monitorEndingListeners() +void MusicListenersManager::monitorEndingListeners(int tracksCount) { --d->mActiveMusicListenersCount; + d->mTotalImportedTracksCount += tracksCount; + if (d->mActiveMusicListenersCount == 0) { - if (d->mImportedTracksCount < 4 && d->mElisaApplication) { + if (d->mTotalImportedTracksCount < 4 && d->mElisaApplication) { NotificationItem notEnoughTracks; notEnoughTracks.setNotificationId(QStringLiteral("notEnoughTracks")); notEnoughTracks.setTargetObject(this); notEnoughTracks.setMessage(i18nc("No track found message", "No track have been found")); auto configureAction = d->mElisaApplication->action(QStringLiteral("options_configure")); notEnoughTracks.setMainButtonText(configureAction->text()); notEnoughTracks.setMainButtonIconName(configureAction->icon().name()); notEnoughTracks.setMainButtonMethodName(QStringLiteral("showConfiguration")); Q_EMIT newNotification(notEnoughTracks); } d->mIndexingRunning = false; Q_EMIT indexingRunningChanged(); QMetaObject::invokeMethod(&d->mDatabaseInterface, "cleanInvalidTracks", Qt::QueuedConnection); } } #include "moc_musiclistenersmanager.cpp" diff --git a/src/musiclistenersmanager.h b/src/musiclistenersmanager.h index 5eb58443..b6465a32 100644 --- a/src/musiclistenersmanager.h +++ b/src/musiclistenersmanager.h @@ -1,149 +1,182 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MUSICLISTENERSMANAGER_H #define MUSICLISTENERSMANAGER_H #include "notificationitem.h" #include #include #include "musicalbum.h" #include "musicartist.h" #include "musicaudiotrack.h" #include class MusicListenersManagerPrivate; class DatabaseInterface; class MediaPlayList; class NotificationItem; class ElisaApplication; +class QAbstractItemModel; class MusicListenersManager : public QObject { Q_OBJECT Q_PROPERTY(DatabaseInterface* viewDatabase READ viewDatabase NOTIFY viewDatabaseChanged) Q_PROPERTY(int importedTracksCount READ importedTracksCount NOTIFY importedTracksCountChanged) Q_PROPERTY(bool indexingRunning READ isIndexingRunning NOTIFY indexingRunningChanged) Q_PROPERTY(ElisaApplication* elisaApplication READ elisaApplication WRITE setElisaApplication NOTIFY elisaApplicationChanged) + Q_PROPERTY(QAbstractItemModel* allAlbumsModel + READ allAlbumsModel + NOTIFY allAlbumsModelChanged) + + Q_PROPERTY(QAbstractItemModel* allArtistsModel + READ allArtistsModel + NOTIFY allArtistsModelChanged) + + Q_PROPERTY(QAbstractItemModel* allTracksModel + READ allTracksModel + NOTIFY allTracksModelChanged) + + Q_PROPERTY(bool indexerBusy + READ indexerBusy + NOTIFY indexerBusyChanged) + public: explicit MusicListenersManager(QObject *parent = nullptr); ~MusicListenersManager() override; DatabaseInterface* viewDatabase() const; void subscribeForTracks(MediaPlayList *client); int importedTracksCount() const; bool isIndexingRunning() const; ElisaApplication* elisaApplication() const; + QAbstractItemModel *allAlbumsModel() const; + + QAbstractItemModel *allArtistsModel() const; + + QAbstractItemModel *allTracksModel() const; + + bool indexerBusy() const; + Q_SIGNALS: void viewDatabaseChanged(); void artistAdded(const MusicArtist &newArtist); void albumAdded(const MusicAlbum &newAlbum); void trackAdded(qulonglong id); void tracksAdded(const QList &allTracks); void artistRemoved(const MusicArtist &removedArtist); void albumRemoved(const MusicAlbum &removedAlbum, qulonglong removedAlbumId); void trackRemoved(qulonglong id); void artistModified(const MusicArtist &modifiedArtist); void albumModified(const MusicAlbum &modifiedAlbum, qulonglong modifiedAlbumId); void trackModified(const MusicAudioTrack &modifiedTrack); void applicationIsTerminating(); void importedTracksCountChanged(); void indexingRunningChanged(); void newNotification(NotificationItem notification); void closeNotification(QString notificationId); void elisaApplicationChanged(); void removeTracksInError(QList tracks); void displayTrackError(const QString &fileName); + void allAlbumsModelChanged(); + + void allArtistsModelChanged(); + + void allTracksModelChanged(); + + void indexerBusyChanged(); + public Q_SLOTS: void databaseReady(); void applicationAboutToQuit(); void showConfiguration(); void resetImportedTracksCounter(); void setElisaApplication(ElisaApplication* elisaApplication); void playBackError(QUrl sourceInError, QMediaPlayer::Error playerError); private Q_SLOTS: void configChanged(); void computeImportedTracksCount(); void monitorStartingListeners(); - void monitorEndingListeners(); + void monitorEndingListeners(int tracksCount); private: std::unique_ptr d; }; #endif // MUSICLISTENERSMANAGER_H diff --git a/src/qml/ElisaMainWindow.qml b/src/qml/ElisaMainWindow.qml index 7c770d1f..12a586cf 100644 --- a/src/qml/ElisaMainWindow.qml +++ b/src/qml/ElisaMainWindow.qml @@ -1,1087 +1,1006 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.7 import QtQuick.Controls 1.3 import QtQuick.Controls.Styles 1.3 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 import Qt.labs.platform 1.0 as PlatformDialog import org.kde.elisa 1.0 import Qt.labs.settings 1.0 ApplicationWindow { id: mainWindow visible: true minimumWidth: 1000 minimumHeight: 600 LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft LayoutMirroring.childrenInherit: true x: persistentSettings.x y: persistentSettings.y width: persistentSettings.width height: persistentSettings.height title: 'Elisa' property var helpAction: elisa.action("help_contents") property var quitApplication: elisa.action("file_quit") property var reportBugAction: elisa.action("help_report_bug") property var aboutAppAction: elisa.action("help_about_app") property var configureShortcutsAction: elisa.action("options_configure_keybinding") property var configureAction: elisa.action("options_configure") property var goBackAction: elisa.action("go_back") SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } Settings { id: persistentSettings property int x property int y property int width : 1000 property int height : 600 property var playListState property var playListControlerState property var audioPlayerState property double playControlItemVolume : 100.0 property bool playControlItemMuted : false } Action { text: goBackAction.text shortcut: goBackAction.shortcut iconName: elisa.iconName(goBackAction.icon) onTriggered: { localAlbums.goBack() localArtists.goBack() } } Action { id: qmlQuitAction text: quitApplication.text shortcut: quitApplication.shortcut iconName: elisa.iconName(quitApplication.icon) onTriggered: quitApplication.trigger() } property string globalBrowseFlag: 'BrowseDirectChildren' property string globalFilter: '*' property string globalSortCriteria: '' Connections { target: Qt.application onAboutToQuit: { persistentSettings.x = mainWindow.x; persistentSettings.y = mainWindow.y; persistentSettings.width = mainWindow.width; persistentSettings.height = mainWindow.height; persistentSettings.playListState = playListModelItem.persistentState; persistentSettings.playListControlerState = playListModelItem.persistentState; persistentSettings.audioPlayerState = manageAudioPlayer.persistentState persistentSettings.playControlItemVolume = headerBar.playerControl.volume persistentSettings.playControlItemMuted = headerBar.playerControl.muted } } PlatformIntegration { id: platformInterface playListModel: playListModelItem playListControler: playListModelItem audioPlayerManager: manageAudioPlayer headerBarManager: myHeaderBarManager manageMediaPlayerControl: myPlayControlManager player: audioPlayer onRaisePlayer: { mainWindow.show() mainWindow.raise() mainWindow.requestActivate() } } - MusicListenersManager { - id: allListeners - - elisaApplication: elisa - } - AudioWrapper { id: audioPlayer muted: headerBar.playerControl.muted volume: headerBar.playerControl.volume onVolumeChanged: headerBar.playerControl.volume = volume onMutedChanged: headerBar.playerControl.muted = muted source: manageAudioPlayer.playerSource onPlaying: { myPlayControlManager.playerPlaying() } onPaused: { myPlayControlManager.playerPaused() } onStopped: { myPlayControlManager.playerStopped() } } MediaPlayList { id: playListModelItem persistentState: persistentSettings.playListState musicListenersManager: allListeners onPlayListFinished: manageAudioPlayer.playListFinished() Component.onCompleted: { var d = new Date(); var n = d.getMilliseconds(); seedRandomGenerator(n); playFiles(elisa.arguments) } onPlayListLoadFailed: { messageNotification.showNotification(i18nc("message of passive notification when playlist load failed", "Load of playlist failed"), 3000) } function playFiles(listFiles) { var previousTrackNumber = tracksCount enqueue(listFiles) switchTo(previousTrackNumber) manageAudioPlayer.ensurePlay() } } Connections { target: elisa onEnqueue: { playListModelItem.playFiles(files) } } ManageHeaderBar { id: myHeaderBarManager playListModel: playListModelItem currentTrack: playListModelItem.currentTrack artistRole: MediaPlayList.ArtistRole titleRole: MediaPlayList.TitleRole albumRole: MediaPlayList.AlbumRole imageRole: MediaPlayList.ImageRole isValidRole: MediaPlayList.IsValidRole } ManageAudioPlayer { id: manageAudioPlayer currentTrack: playListModelItem.currentTrack playListModel: playListModelItem urlRole: MediaPlayList.ResourceRole isPlayingRole: MediaPlayList.IsPlayingRole titleRole: MediaPlayList.TitleRole artistNameRole: MediaPlayList.ArtistRole albumNameRole: MediaPlayList.AlbumRole playerStatus: audioPlayer.status playerPlaybackState: audioPlayer.playbackState playerError: audioPlayer.error audioDuration: audioPlayer.duration playerIsSeekable: audioPlayer.seekable playerPosition: audioPlayer.position persistentState: persistentSettings.audioPlayerState onPlayerPlay: audioPlayer.play() onPlayerPause: audioPlayer.pause() onPlayerStop: audioPlayer.stop() onSkipNextTrack: playListModelItem.skipNextTrack() onSeek: audioPlayer.seek(position) onSourceInError: { playListModelItem.trackInError(source, playerError) allListeners.playBackError(source, playerError) } onDisplayTrackError: messageNotification.showNotification(i18n("Error when playing %1", "" + fileName), 3000) } ManageMediaPlayerControl { id: myPlayControlManager playListModel: playListModelItem currentTrack: playListModelItem.currentTrack } - AllAlbumsModel { - id: allAlbumsModel - - allArtists: allArtistsModel - } - - AllArtistsModel { - id: allArtistsModel - - allAlbums: allAlbumsModel - } - - AllTracksModel { - id: allTracksModel - } - - Connections { - target: allListeners - - onAlbumAdded: { - busyScanningMusic.running = false - allAlbumsModel.albumAdded(newAlbum) - } - } - - Connections { - target: allListeners - - onAlbumRemoved: { - allAlbumsModel.albumRemoved(removedAlbum) - } - } - - Connections { - target: allListeners - - onAlbumModified: allAlbumsModel.albumModified(modifiedAlbum) - } - - Connections { - target: allListeners - - onTracksAdded: allTracksModel.tracksAdded(allTracks) - } - - Connections { - target: allListeners - - onTrackRemoved: allTracksModel.trackRemoved(id) - } - - Connections { - target: allListeners - - onTrackModified: allTracksModel.trackModified(modifiedTrack) - } - - Connections { - target: allListeners - - onArtistAdded: allArtistsModel.artistAdded(newArtist) - } - - Connections { - target: allListeners - - onArtistRemoved: allArtistsModel.artistRemoved(removedArtist) - } - - Connections { - target: allListeners - - onArtistModified: allArtistsModel.artistModified(modifiedArtist) - } - Menu { id: applicationMenu title: i18nc("open application menu", "Application Menu") MenuItem { text: configureAction.text shortcut: configureAction.shortcut iconName: 'configure' onTriggered: configureAction.trigger() visible: configureAction.text !== "" } MenuItem { text: configureShortcutsAction.text shortcut: configureShortcutsAction.shortcut iconName: elisa.iconName(configureShortcutsAction.icon) onTriggered: configureShortcutsAction.trigger() visible: configureShortcutsAction.text !== "" } MenuSeparator { visible: reportBugAction.text !== "" } MenuItem { text: reportBugAction.text shortcut: reportBugAction.shortcut iconName: elisa.iconName(reportBugAction.icon) onTriggered: reportBugAction.trigger() visible: reportBugAction.text !== "" } MenuSeparator { visible: helpAction.text !== "" } MenuItem { text: helpAction.text shortcut: helpAction.shortcut iconName: elisa.iconName(helpAction.icon) onTriggered: helpAction.trigger() visible: helpAction.text !== "" } MenuItem { text: aboutAppAction.text shortcut: aboutAppAction.shortcut iconName: elisa.iconName(aboutAppAction.icon) onTriggered: aboutAppAction.trigger() visible: aboutAppAction.text !== "" } MenuSeparator { visible: qmlQuitAction.text !== "" } MenuItem { action: qmlQuitAction visible: qmlQuitAction.text !== "" } } Action { id: applicationMenuAction text: i18nc("open application menu", "Application Menu") iconName: "application-menu" onTriggered: applicationMenu.popup() } PassiveNotification { id: messageNotification } Rectangle { color: myPalette.base anchors.fill: parent ColumnLayout { anchors.fill: parent spacing: 0 Item { Layout.preferredHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight Layout.minimumHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight Layout.maximumHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight Layout.fillWidth: true HeaderBar { id: headerBar focus: true anchors.fill: parent tracksCount: myHeaderBarManager.remainingTracks album: myHeaderBarManager.album title: myHeaderBarManager.title artist: myHeaderBarManager.artist image: myHeaderBarManager.image ratingVisible: false playerControl.duration: audioPlayer.duration playerControl.seekable: audioPlayer.seekable playerControl.volume: persistentSettings.playControlItemVolume playerControl.muted: persistentSettings.playControlItemMuted playerControl.position: audioPlayer.position playerControl.skipBackwardEnabled: myPlayControlManager.skipBackwardControlEnabled playerControl.skipForwardEnabled: myPlayControlManager.skipForwardControlEnabled playerControl.playEnabled: myPlayControlManager.playControlEnabled playerControl.isPlaying: myPlayControlManager.musicPlaying playerControl.onSeek: audioPlayer.seek(position) playerControl.onPlay: manageAudioPlayer.playPause() playerControl.onPause: manageAudioPlayer.playPause() playerControl.onPlayPrevious: playListModelItem.skipPreviousTrack() playerControl.onPlayNext: playListModelItem.skipNextTrack() ToolButton { id: menuButton action: applicationMenuAction z: 2 anchors { right: parent.right top: parent.top rightMargin: elisaTheme.layoutHorizontalMargin * 3 topMargin: elisaTheme.layoutHorizontalMargin * 3 } } Rectangle { anchors.fill: menuButton z: 1 radius: width / 2 color: myPalette.window } TrackImportNotification { id: importedTracksCountNotification anchors { right: menuButton.left top: menuButton.top bottom: menuButton.bottom rightMargin: elisaTheme.layoutHorizontalMargin * 3 } indexingRunning: allListeners.indexingRunning importedTracksCount: allListeners.importedTracksCount musicManager: allListeners } } } RowLayout { Layout.fillHeight: true Layout.fillWidth: true spacing: 0 ViewSelector { id: listViews Layout.fillHeight: true Layout.preferredWidth: mainWindow.width * 0.15 Layout.maximumWidth: mainWindow.width * 0.15 } ColumnLayout { Layout.fillHeight: true Layout.fillWidth: true spacing: 0 TopNotification { id: invalidBalooConfiguration Layout.fillWidth: true musicManager: allListeners focus: true } Item { Layout.fillHeight: true Layout.fillWidth: true RowLayout { anchors.fill: parent spacing: 0 id: contentZone FocusScope { id: mainContentView focus: true Layout.fillHeight: true Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 visible: Layout.minimumWidth != 0 Rectangle { border { color: (mainContentView.activeFocus ? myPalette.highlight : myPalette.base) width: 1 } radius: 3 color: myPalette.base anchors.fill: parent BusyIndicator { id: busyScanningMusic anchors.fill: parent anchors.leftMargin: parent.width / 3 anchors.rightMargin: parent.width / 3 anchors.topMargin: parent.height / 3 anchors.bottomMargin: parent.height / 3 opacity: 0.8 z: 2 - running: true + running: allListeners.indexerBusy } MediaBrowser { id: localAlbums focus: true anchors { fill: parent leftMargin: elisaTheme.layoutHorizontalMargin rightMargin: elisaTheme.layoutHorizontalMargin } firstPage: GridBrowserView { id: allAlbumsView focus: true isFirstPage: true model: AlbumFilterProxyModel { - sourceModel: allAlbumsModel + sourceModel: allListeners.allAlbumsModel } mainTitle: i18nc("Title of the view of all albums", "Albums") onEnqueue: playListModelItem.enqueue(data) onReplaceAndPlay: { playListModelItem.clearAndEnqueue(data) manageAudioPlayer.ensurePlay() } onOpen: { localAlbums.stackView.push(albumView, { stackView: localAlbums.stackView, albumName: innerMainTitle, albumModel: innerModel, albumArtUrl: innerImage, albumId: databaseId }) } onGoBack: localAlbums.stackView.pop() } visible: opacity > 0 } MediaBrowser { id: localArtists focus: true anchors { fill: parent leftMargin: elisaTheme.layoutHorizontalMargin rightMargin: elisaTheme.layoutHorizontalMargin } firstPage: GridBrowserView { id: allArtistsView focus: true isFirstPage: true showRating: false delegateDisplaySecondaryText: false model: AlbumFilterProxyModel { - sourceModel: allArtistsModel + sourceModel: allListeners.allArtistsModel } mainTitle: i18nc("Title of the view of all artists", "Artists") onEnqueue: playListModelItem.enqueue(data) onReplaceAndPlay: { playListModelItem.clearAndEnqueue(data) manageAudioPlayer.ensurePlay() } onOpen: { localArtists.stackView.push(innerAlbumView, { model: innerModel, mainTitle: innerMainTitle, secondaryTitle: innerSecondaryTitle, image: innerImage, stackView: localArtists.stackView }) } onGoBack: localArtists.stackView.pop() } visible: opacity > 0 } MediaBrowser { id: localTracks focus: true anchors { fill: parent leftMargin: elisaTheme.layoutHorizontalMargin rightMargin: elisaTheme.layoutHorizontalMargin } firstPage: MediaAllTracksView { focus: true stackView: localTracks.stackView model: AlbumFilterProxyModel { - sourceModel: allTracksModel + sourceModel: allListeners.allTracksModel } onEnqueue: playListModelItem.enqueue(data) onReplaceAndPlay: { playListModelItem.clearAndEnqueue(data) manageAudioPlayer.ensurePlay() } } visible: opacity > 0 } Behavior on border.color { ColorAnimation { duration: 300 } } } } Rectangle { id: firstViewSeparatorItem border.width: 1 border.color: myPalette.mid color: myPalette.mid visible: true Layout.bottomMargin: elisaTheme.layoutVerticalMargin Layout.topMargin: elisaTheme.layoutVerticalMargin Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter Layout.fillHeight: true Layout.preferredWidth: 1 Layout.minimumWidth: 1 Layout.maximumWidth: 1 } MediaPlayListView { id: playList playListModel: playListModelItem playListControler: playListModelItem randomPlayChecked: playListModelItem.randomPlay repeatPlayChecked: playListModelItem.repeatPlay Layout.fillHeight: true Layout.leftMargin: elisaTheme.layoutHorizontalMargin Layout.rightMargin: elisaTheme.layoutHorizontalMargin Layout.minimumWidth: contentZone.width Layout.maximumWidth: contentZone.width Layout.preferredWidth: contentZone.width Component.onCompleted: { playListModelItem.randomPlay = Qt.binding(function() { return playList.randomPlayChecked }) playListModelItem.repeatPlay = Qt.binding(function() { return playList.repeatPlayChecked }) myPlayControlManager.randomOrContinuePlay = Qt.binding(function() { return playList.randomPlayChecked || playList.repeatPlayChecked }) } onStartPlayback: manageAudioPlayer.ensurePlay() onPausePlayback: manageAudioPlayer.playPause() onDisplayError: messageNotification.showNotification(errorText) } Rectangle { id: viewSeparatorItem border.width: 1 border.color: myPalette.mid color: myPalette.mid visible: Layout.minimumWidth != 0 Layout.bottomMargin: elisaTheme.layoutVerticalMargin Layout.topMargin: elisaTheme.layoutVerticalMargin Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter Layout.fillHeight: true Layout.preferredWidth: 1 Layout.minimumWidth: 1 Layout.maximumWidth: 1 } ContextView { id: albumContext Layout.fillHeight: true Layout.minimumWidth: contentZone.width Layout.maximumWidth: contentZone.width Layout.preferredWidth: contentZone.width visible: Layout.minimumWidth != 0 artistName: myHeaderBarManager.artist albumName: myHeaderBarManager.album albumArtUrl: myHeaderBarManager.image } } } states: [ State { name: 'full' when: listViews.currentIndex === 0 PropertyChanges { target: mainContentView Layout.fillWidth: false Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: firstViewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: playList Layout.minimumWidth: contentZone.width / 2 Layout.maximumWidth: contentZone.width / 2 Layout.preferredWidth: contentZone.width / 2 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: albumContext Layout.minimumWidth: contentZone.width / 2 Layout.maximumWidth: contentZone.width / 2 Layout.preferredWidth: contentZone.width / 2 } PropertyChanges { target: localAlbums opacity: 0 } PropertyChanges { target: localArtists opacity: 0 } PropertyChanges { target: localTracks opacity: 0 } }, State { name: 'allAlbums' when: listViews.currentIndex === 1 StateChangeScript { script: { localAlbums.stackView.pop({item: null, immediate: true}) } } PropertyChanges { target: mainContentView Layout.fillWidth: true Layout.minimumWidth: contentZone.width * 0.66 Layout.maximumWidth: contentZone.width * 0.68 Layout.preferredWidth: contentZone.width * 0.68 } PropertyChanges { target: firstViewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: playList Layout.minimumWidth: contentZone.width * 0.33 Layout.maximumWidth: contentZone.width * 0.33 Layout.preferredWidth: contentZone.width * 0.33 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: albumContext Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: localAlbums opacity: 1 } PropertyChanges { target: localArtists opacity: 0 } PropertyChanges { target: localTracks opacity: 0 } }, State { name: 'allArtists' when: listViews.currentIndex === 2 StateChangeScript { script: { localArtists.stackView.pop({item: null, immediate: true}) } } PropertyChanges { target: mainContentView Layout.fillWidth: true Layout.minimumWidth: contentZone.width * 0.66 Layout.maximumWidth: contentZone.width * 0.68 Layout.preferredWidth: contentZone.width * 0.68 } PropertyChanges { target: firstViewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: playList Layout.minimumWidth: contentZone.width * 0.33 Layout.maximumWidth: contentZone.width * 0.33 Layout.preferredWidth: contentZone.width * 0.33 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: albumContext Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: localAlbums opacity: 0 } PropertyChanges { target: localArtists opacity: 1 } PropertyChanges { target: localTracks opacity: 0 } }, State { name: 'allTracks' when: listViews.currentIndex === 3 PropertyChanges { target: mainContentView Layout.fillWidth: true Layout.minimumWidth: contentZone.width * 0.66 Layout.maximumWidth: contentZone.width * 0.68 Layout.preferredWidth: contentZone.width * 0.68 } PropertyChanges { target: firstViewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: playList Layout.minimumWidth: contentZone.width * 0.33 Layout.maximumWidth: contentZone.width * 0.33 Layout.preferredWidth: contentZone.width * 0.33 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: albumContext Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: localAlbums opacity: 0 } PropertyChanges { target: localArtists opacity: 0 } PropertyChanges { target: localTracks opacity: 1 } } ] transitions: Transition { NumberAnimation { properties: "Layout.minimumWidth, Layout.maximumWidth, Layout.preferredWidth, opacity" easing.type: Easing.InOutQuad duration: 300 } } } } } } Component { id: innerAlbumView GridBrowserView { property var stackView onEnqueue: playListModelItem.enqueue(data) onReplaceAndPlay: { playListModelItem.clearAndEnqueue(data) manageAudioPlayer.ensurePlay() } onOpen: { localArtists.stackView.push(albumView, { stackView: localArtists.stackView, albumName: innerMainTitle, albumModel: innerModel, albumArtUrl: innerImage, albumId: databaseId }) } onGoBack: stackView.pop() } } Component { id: albumView MediaAlbumView { property var stackView onEnqueue: playListModelItem.enqueue(data) onReplaceAndPlay: { playListModelItem.clearAndEnqueue(data) manageAudioPlayer.ensurePlay() } onShowArtist: { listViews.currentIndex = 2 if (localArtists.stackView.depth === 3) { localArtists.stackView.pop() } if (localArtists.stackView.depth === 2) { var artistPage = localArtists.stackView.get(1) if (artistPage.mainTitle === name) { return } else { localArtists.stackView.pop() } } - allArtistsView.open(allArtistsModel.itemModelForName(name), name, '', elisaTheme.defaultArtistImage, '') + allArtistsView.open(allListeners.allArtistsModel.itemModelForName(name), name, '', elisaTheme.defaultArtistImage, '') } onGoBack: stackView.pop() Connections { target: allListeners onAlbumRemoved: if (albumId === removedAlbumId) { removeAlbum(removedAlbum) } } Connections { target: allListeners onAlbumModified: if (albumId === modifiedAlbumId) { modifyAlbum(modifiedAlbum) } } } } }