diff --git a/CMakeLists.txt b/CMakeLists.txt index 523ff0c2..c8ce6d69 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,77 +1,77 @@ cmake_minimum_required(VERSION 3.5) project(elisa) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD 14) set(REQUIRED_QT_VERSION "5.7.0") find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Core Network Xml Qml Sql Multimedia Svg Gui) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG QUIET OPTIONAL_COMPONENTS Quick Widgets Test DBus WebSockets AndroidExtras) set(REQUIRED_KF5_VERSION "5.32.0") find_package(ECM ${REQUIRED_KF5_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(KDEInstallDirs) include(KDEFrameworkCompilerSettings) include(KDECMakeSettings) include(ECMInstallIcons) include(FeatureSummary) find_package(KF5I18n ${REQUIRED_KF5_VERSION} CONFIG REQUIRED QUIET) find_package(KF5Declarative ${REQUIRED_KF5_VERSION} CONFIG QUIET) find_package(KF5CoreAddons ${REQUIRED_KF5_VERSION} CONFIG QUIET) find_package(KF5Baloo ${REQUIRED_KF5_VERSION} CONFIG QUIET) find_package(KF5FileMetaData ${REQUIRED_KF5_VERSION} CONFIG QUIET) find_package(KF5DocTools ${REQUIRED_KF5_VERSION} CONFIG QUIET) find_package(KF5XmlGui ${REQUIRED_KF5_VERSION} CONFIG QUIET) -find_package(KF5ConfigWidgets ${REQUIRED_KF5_VERSION} CONFIG QUIET) -find_package(KF5Config ${REQUIRED_KF5_VERSION} CONFIG QUIET) +find_package(KF5Config ${REQUIRED_KF5_VERSION} CONFIG REQUIRED QUIET) find_package(KF5Crash ${REQUIRED_KF5_VERSION} CONFIG QUIET) find_package(KF5DBusAddons ${REQUIRED_KF5_VERSION} CONFIG QUIET) +find_package(KF5KCMUtils ${REQUIRED_KF5_VERSION} CONFIG REQUIRED QUIET) find_package(UPNPQT CONFIG QUIET) set_package_properties(UPNPQT PROPERTIES DESCRIPTION "UPNP layer build with Qt 5." URL "https://gitlab.com/homeautomationqt/upnp-player-qt" TYPE OPTIONAL) if (UPNPQT_FOUND) message(WARNING "UPnP support is experimental and may not work.") endif() include(FeatureSummary) include(GenerateExportHeader) include(ECMSetupVersion) include(ECMGenerateHeaders) include(CMakePackageConfigHelpers) if (CMAKE_SYSTEM_NAME STREQUAL Android) set(QT_QMAKE_EXECUTABLE "$ENV{Qt5_android}/bin/qmake") # workaround until this fix is in released ECM if(ECM_VERSION VERSION_LESS "5.24.0") add_definitions(-DANDROID) endif() endif() configure_file(config-upnp-qt.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-upnp-qt.h ) add_subdirectory(src) add_subdirectory(icons) add_subdirectory(autotests) add_subdirectory(doc) install( PROGRAMS org.kde.elisa.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) install( FILES org.kde.elisa.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR} ) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 453c6cbe..d41e3593 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,343 +1,373 @@ enable_testing() 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 ) add_executable(databaseInterfaceTest ${databaseInterfaceTest_SOURCES}) target_link_libraries(databaseInterfaceTest Qt5::Test Qt5::Core Qt5::Sql KF5::I18n) target_include_directories(databaseInterfaceTest PRIVATE ${CMAKE_SOURCE_DIR}/src) add_test(databaseInterfaceTest databaseInterfaceTest) set(playListControlerTest_SOURCES ../src/playlistcontroler.cpp ../src/mediaplaylist.cpp ../src/databaseinterface.cpp ../src/musiclistenersmanager.cpp ../src/trackslistener.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp playlistcontrolertest.cpp ) if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) set(playListControlerTest_SOURCES ${playListControlerTest_SOURCES} ../src/baloo/baloolistener.cpp ../src/baloo/localbaloofilelisting.cpp ) endif() endif() if (UPNPQT_FOUND) set(playListControlerTest_SOURCES ${playListControlerTest_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() if (KF5FileMetaData_FOUND) set(playListControlerTest_SOURCES ${playListControlerTest_SOURCES} ../src/file/filelistener.cpp ../src/file/localfilelisting.cpp ../src/abstractfile/abstractfilelistener.cpp ../src/abstractfile/abstractfilelisting.cpp ) endif() +kconfig_add_kcfg_files(playListControlerTest_SOURCES ../src/elisa_settings.kcfgc ) +set(playListControlerTest_SOURCES + ${playListControlerTest_SOURCES} + ../src/elisa_core.kcfg +) + add_executable(playListControlerTest ${playListControlerTest_SOURCES}) -target_link_libraries(playListControlerTest Qt5::Test Qt5::Core Qt5::Sql KF5::I18n) +target_link_libraries(playListControlerTest Qt5::Test Qt5::Core Qt5::Sql KF5::I18n KF5::ConfigCore KF5::ConfigGui) if (KF5Baloo_FOUND) target_link_libraries(playListControlerTest KF5::Baloo Qt5::DBus) endif() if (KF5FileMetaData_FOUND) target_link_libraries(playListControlerTest KF5::FileMetaData) endif() if (UPNPQT_FOUND) target_link_libraries(playListControlerTest Qt5::Xml UPNP::upnpQt Qt5::Network) endif() target_include_directories(playListControlerTest PRIVATE ${CMAKE_SOURCE_DIR}/src) add_test(playListControlerTest playListControlerTest) set(managemediaplayercontrolTest_SOURCES ../src/managemediaplayercontrol.cpp ../src/mediaplaylist.cpp ../src/databaseinterface.cpp ../src/musiclistenersmanager.cpp ../src/trackslistener.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp managemediaplayercontroltest.cpp ) if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) 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() if (KF5FileMetaData_FOUND) set(managemediaplayercontrolTest_SOURCES ${managemediaplayercontrolTest_SOURCES} ../src/file/filelistener.cpp ../src/file/localfilelisting.cpp ../src/abstractfile/abstractfilelistener.cpp ../src/abstractfile/abstractfilelisting.cpp ) endif() +kconfig_add_kcfg_files(managemediaplayercontrolTest_SOURCES ../src/elisa_settings.kcfgc ) +set(managemediaplayercontrolTest_SOURCES + ${managemediaplayercontrolTest_SOURCES} + ../src/elisa_core.kcfg +) + add_executable(managemediaplayercontrolTest ${managemediaplayercontrolTest_SOURCES}) -target_link_libraries(managemediaplayercontrolTest Qt5::Test Qt5::Core Qt5::Sql KF5::I18n) +target_link_libraries(managemediaplayercontrolTest Qt5::Test Qt5::Core Qt5::Sql KF5::I18n KF5::ConfigCore KF5::ConfigGui) if (KF5Baloo_FOUND) target_link_libraries(managemediaplayercontrolTest KF5::Baloo Qt5::DBus) endif() if (KF5FileMetaData_FOUND) target_link_libraries(managemediaplayercontrolTest KF5::FileMetaData) endif() if (UPNPQT_FOUND) target_link_libraries(managemediaplayercontrolTest Qt5::Xml UPNP::upnpQt Qt5::Network) endif() target_include_directories(managemediaplayercontrolTest PRIVATE ${CMAKE_SOURCE_DIR}/src) add_test(managemediaplayercontrolTest managemediaplayercontrolTest) set(manageheaderbarTest_SOURCES ../src/manageheaderbar.cpp ../src/mediaplaylist.cpp ../src/databaseinterface.cpp ../src/musiclistenersmanager.cpp ../src/trackslistener.cpp ../src/trackslistener.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp manageheaderbartest.cpp ) if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) 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() if (KF5FileMetaData_FOUND) set(manageheaderbarTest_SOURCES ${manageheaderbarTest_SOURCES} ../src/file/filelistener.cpp ../src/file/localfilelisting.cpp ../src/abstractfile/abstractfilelistener.cpp ../src/abstractfile/abstractfilelisting.cpp ) endif() +kconfig_add_kcfg_files(manageheaderbarTest_SOURCES ../src/elisa_settings.kcfgc ) +set(manageheaderbarTest_SOURCES + ${manageheaderbarTest_SOURCES} + ../src/elisa_core.kcfg +) + add_executable(manageheaderbarTest ${manageheaderbarTest_SOURCES}) -target_link_libraries(manageheaderbarTest Qt5::Test Qt5::Core Qt5::Sql Qt5::Gui KF5::I18n) +target_link_libraries(manageheaderbarTest Qt5::Test Qt5::Core Qt5::Sql Qt5::Gui KF5::I18n KF5::ConfigCore KF5::ConfigGui) if (KF5Baloo_FOUND) target_link_libraries(manageheaderbarTest KF5::Baloo Qt5::DBus) endif() if (KF5FileMetaData_FOUND) target_link_libraries(manageheaderbarTest KF5::FileMetaData) endif() if (UPNPQT_FOUND) target_link_libraries(manageheaderbarTest Qt5::Xml UPNP::upnpQt Qt5::Network) endif() target_include_directories(manageheaderbarTest PRIVATE ${CMAKE_SOURCE_DIR}/src) add_test(manageheaderbarTest manageheaderbarTest) set(manageaudioplayerTest_SOURCES ../src/manageaudioplayer.cpp manageaudioplayertest.cpp ) add_executable(manageaudioplayerTest ${manageaudioplayerTest_SOURCES}) target_link_libraries(manageaudioplayerTest Qt5::Test Qt5::Core Qt5::Gui) target_include_directories(manageaudioplayerTest PRIVATE ${CMAKE_SOURCE_DIR}/src) add_test(manageaudioplayerTest manageaudioplayerTest) set(mediaplaylistTest_SOURCES ../src/mediaplaylist.cpp ../src/databaseinterface.cpp ../src/trackslistener.cpp ../src/musiclistenersmanager.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp mediaplaylisttest.cpp ) if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) 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() if (KF5FileMetaData_FOUND) set(mediaplaylistTest_SOURCES ${mediaplaylistTest_SOURCES} ../src/file/filelistener.cpp ../src/file/localfilelisting.cpp ../src/abstractfile/abstractfilelistener.cpp ../src/abstractfile/abstractfilelisting.cpp ) endif() +kconfig_add_kcfg_files(mediaplaylistTest_SOURCES ../src/elisa_settings.kcfgc ) +set(mediaplaylistTest_SOURCES + ${mediaplaylistTest_SOURCES} + ../src/elisa_core.kcfg +) + add_executable(mediaplaylistTest ${mediaplaylistTest_SOURCES}) -target_link_libraries(mediaplaylistTest Qt5::Test Qt5::Core Qt5::Sql Qt5::Gui KF5::I18n) +target_link_libraries(mediaplaylistTest Qt5::Test Qt5::Core Qt5::Sql Qt5::Gui KF5::I18n KF5::ConfigCore KF5::ConfigGui) if (KF5Baloo_FOUND) target_link_libraries(mediaplaylistTest KF5::Baloo Qt5::DBus) endif() if (KF5FileMetaData_FOUND) target_link_libraries(mediaplaylistTest KF5::FileMetaData) endif() if (UPNPQT_FOUND) target_link_libraries(mediaplaylistTest Qt5::Xml UPNP::upnpQt Qt5::Network) endif() target_include_directories(mediaplaylistTest PRIVATE ${CMAKE_SOURCE_DIR}/src) add_test(mediaplaylistTest mediaplaylistTest) set(allalbumsmodeltest_SOURCES ../src/databaseinterface.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp ../src/allalbumsmodel.cpp allalbumsmodeltest.cpp ) add_executable(allalbumsmodeltest ${allalbumsmodeltest_SOURCES}) target_link_libraries(allalbumsmodeltest Qt5::Test Qt5::Core Qt5::Sql KF5::I18n) target_include_directories(allalbumsmodeltest PRIVATE ${CMAKE_SOURCE_DIR}/src) add_test(allalbumsmodeltest allalbumsmodeltest) set(albummodeltest_SOURCES ../src/databaseinterface.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp ../src/albummodel.cpp albummodeltest.cpp ) add_executable(albummodeltest ${albummodeltest_SOURCES}) target_link_libraries(albummodeltest Qt5::Test Qt5::Core Qt5::Sql KF5::I18n) target_include_directories(albummodeltest PRIVATE ${CMAKE_SOURCE_DIR}/src) add_test(albummodeltest albummodeltest) set(allartistsmodeltest_SOURCES ../src/databaseinterface.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp ../src/allartistsmodel.cpp allartistsmodeltest.cpp ) add_executable(allartistsmodeltest ${allartistsmodeltest_SOURCES}) target_link_libraries(allartistsmodeltest Qt5::Test Qt5::Core Qt5::Sql KF5::I18n) target_include_directories(allartistsmodeltest PRIVATE ${CMAKE_SOURCE_DIR}/src) add_test(allartistsmodeltest allartistsmodeltest) set(alltracksmodeltest_SOURCES ../src/databaseinterface.cpp ../src/musicartist.cpp ../src/musicalbum.cpp ../src/musicaudiotrack.cpp ../src/alltracksmodel.cpp alltracksmodeltest.cpp ) add_executable(alltracksmodeltest ${alltracksmodeltest_SOURCES}) target_link_libraries(alltracksmodeltest Qt5::Test Qt5::Core Qt5::Sql KF5::I18n) target_include_directories(alltracksmodeltest PRIVATE ${CMAKE_SOURCE_DIR}/src) add_test(alltracksmodeltest alltracksmodeltest) if (KF5FileMetaData_FOUND) set(localfilelistingtest_SOURCES ../src/file/localfilelisting.cpp ../src/abstractfile/abstractfilelisting.cpp ../src/musicaudiotrack.cpp localfilelistingtest.cpp ) + kconfig_add_kcfg_files(localfilelistingtest_SOURCES ../src/elisa_settings.kcfgc ) + set(localfilelistingtest_SOURCES + ${localfilelistingtest_SOURCES} + ../src/elisa_core.kcfg + ) + add_executable(localfilelistingtest ${localfilelistingtest_SOURCES}) - target_link_libraries(localfilelistingtest Qt5::Test Qt5::Core Qt5::Sql KF5::I18n) - if (KF5FileMetaData_FOUND) - target_link_libraries(localfilelistingtest KF5::FileMetaData) - endif() + target_link_libraries(localfilelistingtest Qt5::Test Qt5::Core Qt5::Sql KF5::I18n KF5::ConfigCore KF5::ConfigGui KF5::FileMetaData) + target_include_directories(localfilelistingtest PRIVATE ${CMAKE_SOURCE_DIR}/src) add_test(localfilelistingtest localfilelistingtest) + endif() diff --git a/config-upnp-qt.h.cmake b/config-upnp-qt.h.cmake index 66287742..80316c51 100644 --- a/config-upnp-qt.h.cmake +++ b/config-upnp-qt.h.cmake @@ -1,29 +1,25 @@ #cmakedefine01 KF5Declarative_FOUND #cmakedefine01 KF5I18n_FOUND #cmakedefine01 KF5CoreAddons_FOUND #cmakedefine01 KF5Baloo_FOUND #cmakedefine01 KF5XmlGui_FOUND -#cmakedefine01 KF5ConfigWidgets_FOUND - -#cmakedefine01 KF5Config_FOUND - #cmakedefine01 KF5Crash_FOUND #cmakedefine01 KF5FileMetaData_FOUND #cmakedefine01 UPNPQT_FOUND #cmakedefine01 Qt5DBus_FOUND #cmakedefine01 Qt5AndroidExtras_FOUND #cmakedefine01 KF5DBusAddons_FOUND #define LOCAL_FILE_TESTS_SAMPLE_FILES_PATH "@CMAKE_CURRENT_SOURCE_DIR@/autotests/data" #define LOCAL_FILE_TESTS_WORKING_PATH "@CMAKE_CURRENT_BINARY_DIR@/autotests/data" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6f21bfd9..aec7d54e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,210 +1,206 @@ include_directories(${elisa_BINARY_DIR}) if (Qt5Quick_FOUND AND Qt5Widgets_FOUND) set(elisa_SOURCES upnpControl.cpp mediaplaylist.cpp playlistcontroler.cpp musicstatistics.cpp musicalbum.cpp musicaudiotrack.cpp musicartist.cpp progressindicator.cpp albummodel.cpp allalbumsmodel.cpp allartistsmodel.cpp databaseinterface.cpp musiclistenersmanager.cpp managemediaplayercontrol.cpp manageheaderbar.cpp manageaudioplayer.cpp albumfilterproxymodel.cpp trackslistener.cpp elisaapplication.cpp audiowrapper.cpp alltracksmodel.cpp MediaServer.qml Theme.qml PlatformIntegration.qml LabelWithToolTip.qml windows/WindowsTheme.qml windows/PlatformIntegration.qml android/MediaServer.qml android/AndroidTheme.qml android/PlatformIntegration.qml RatingStar.qml AudioTrackDelegate.qml PlayListEntry.qml MediaAlbumDelegate.qml MediaArtistDelegate.qml MediaBrowser.qml DraggableItem.qml PassiveNotification.qml HeaderBar.qml NavigationActionBar.qml MediaPlayerControl.qml ContextView.qml MediaArtistAlbumView.qml MediaPlayListView.qml MediaAlbumView.qml MediaAllAlbumView.qml MediaAllArtistView.qml MediaAllTracksView.qml MediaTracksDelegate.qml ) if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) set(elisa_SOURCES ${elisa_SOURCES} baloo/localbaloofilelisting.cpp baloo/baloolistener.cpp ) endif() endif() if (Qt5DBus_FOUND) set(elisa_SOURCES ${elisa_SOURCES} mpris2/mpris2.cpp mpris2/mediaplayer2.cpp mpris2/mediaplayer2player.cpp ) endif() if (UPNPQT_FOUND) set(elisa_SOURCES ${elisa_SOURCES} upnp/upnpcontrolcontentdirectory.cpp upnp/upnpcontentdirectorymodel.cpp upnp/upnpcontrolconnectionmanager.cpp upnp/upnpcontrolmediaserver.cpp upnp/didlparser.cpp upnp/upnplistener.cpp upnp/upnpdiscoverallmusic.cpp ) endif() if (KF5FileMetaData_FOUND) set(elisa_SOURCES ${elisa_SOURCES} abstractfile/abstractfilelistener.cpp abstractfile/abstractfilelisting.cpp file/filelistener.cpp file/localfilelisting.cpp ) endif() + kconfig_add_kcfg_files(elisa_SOURCES elisa_settings.kcfgc) + set(elisa_SOURCES + ${elisa_SOURCES} + elisa_core.kcfg + ) + qt5_add_resources(elisa_SOURCES upnpControl.qrc) add_executable(elisa ${elisa_SOURCES}) set_target_properties(elisa PROPERTIES LINK_FLAGS "-Wl,--no-undefined") target_include_directories(elisa PRIVATE ${KDSoap_INCLUDE_DIRS}) target_link_libraries(elisa LINK_PRIVATE Qt5::Quick Qt5::Gui Qt5::Widgets Qt5::Multimedia Qt5::Svg Qt5::Xml Qt5::Sql KF5::I18n + KF5::ConfigCore + KF5::KCMUtils ) if (Qt5DBus_FOUND) target_link_libraries(elisa LINK_PRIVATE Qt5::DBus ) endif() if (Qt5AndroidExtras_FOUND) target_link_libraries(elisa LINK_PRIVATE Qt5::AndroidExtras ) endif() if (KF5FileMetaData_FOUND) target_link_libraries(elisa LINK_PRIVATE KF5::FileMetaData ) endif() if (KF5CoreAddons_FOUND) target_link_libraries(elisa LINK_PRIVATE KF5::CoreAddons ) endif() if (KF5Baloo_FOUND) target_link_libraries(elisa LINK_PRIVATE KF5::Baloo ) endif() if (KF5Declarative_FOUND) target_link_libraries(elisa LINK_PRIVATE KF5::Declarative ) endif() if (KF5XmlGui_FOUND) target_link_libraries(elisa LINK_PRIVATE KF5::XmlGui ) endif() - if (KF5ConfigWidgets_FOUND) - target_link_libraries(elisa - LINK_PRIVATE - KF5::ConfigWidgets - ) - endif() - - if (KF5Config_FOUND) - target_link_libraries(elisa - LINK_PRIVATE - KF5::ConfigCore - ) - endif() - if (KF5Crash_FOUND) target_link_libraries(elisa LINK_PRIVATE KF5::Crash ) endif() if (UPNPQT_FOUND) target_link_libraries(elisa LINK_PRIVATE UPNP::upnpQt Qt5::Network ) endif() if (KF5DBusAddons_FOUND) target_link_libraries(elisa LINK_PRIVATE KF5::DBusAddons ) endif() endif() install(TARGETS elisa ${INSTALL_TARGETS_DEFAULT_ARGS}) + +add_subdirectory(localFileConfiguration) diff --git a/src/MediaServer.qml b/src/MediaServer.qml index b7caf655..7eb98f04 100644 --- a/src/MediaServer.qml +++ b/src/MediaServer.qml @@ -1,892 +1,901 @@ /* * 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 QtQml.Models 2.1 import org.mgallien.QmlExtension 1.0 import Qt.labs.settings 1.0 ApplicationWindow { id: mainWindow visible: true minimumWidth: 1000 minimumHeight: 600 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") 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 : 1.0 property bool playControlItemMuted : false } 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 = playListControlerItem.persistentState; persistentSettings.audioPlayerState = manageAudioPlayer.persistentState persistentSettings.playControlItemVolume = headerBar.playerControl.volume persistentSettings.playControlItemMuted = headerBar.playerControl.muted } } PlatformIntegration { id: platformInterface playListModel: playListModelItem playListControler: playListControlerItem audioPlayerManager: manageAudioPlayer headerBarManager: myHeaderBarManager manageMediaPlayerControl: myPlayControlManager player: audioPlayer onRaisePlayer: { mainWindow.show() mainWindow.raise() mainWindow.requestActivate() } } MusicListenersManager { id: allListeners } AudioWrapper { id: audioPlayer muted: headerBar.playerControl.muted volume: headerBar.playerControl.volume * 100 onVolumeChanged: headerBar.playerControl.volume = volume / 100.0 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 } PlayListControler { id: playListControlerItem playListModel: playListModelItem isValidRole: MediaPlayList.IsValidRole onPlayListFinished: manageAudioPlayer.playListFinished() persistentState: persistentSettings.playListControlerState Component.onCompleted: { var d = new Date(); var n = d.getMilliseconds(); seedRandomGenerator(n); } } ManageHeaderBar { id: myHeaderBarManager playListModel: playListModelItem currentTrack: playListControlerItem.currentTrack artistRole: MediaPlayList.ArtistRole titleRole: MediaPlayList.TitleRole albumRole: MediaPlayList.AlbumRole imageRole: MediaPlayList.ImageRole isValidRole: MediaPlayList.IsValidRole } ManageAudioPlayer { id: manageAudioPlayer currentTrack: playListControlerItem.currentTrack playListModel: playListModelItem urlRole: MediaPlayList.ResourceRole isPlayingRole: MediaPlayList.IsPlayingRole 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: playListControlerItem.skipNextTrack() onSeek: audioPlayer.seek(position) } ManageMediaPlayerControl { id: myPlayControlManager playListModel: playListModelItem currentTrack: playListControlerItem.currentTrack } AllAlbumsModel { id: 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) } AllArtistsModel { id: allArtistsModel } 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 { action: qmlQuitAction visible: qmlQuitAction.text !== "" } MenuSeparator { visible: qmlQuitAction.text !== "" } MenuItem { text: helpAction.text shortcut: helpAction.shortcut iconName: elisa.iconName(helpAction.icon) onTriggered: helpAction.trigger() visible: helpAction.text !== "" } MenuSeparator { visible: helpAction.text !== "" } MenuItem { text: reportBugAction.text shortcut: reportBugAction.shortcut iconName: elisa.iconName(reportBugAction.icon) onTriggered: reportBugAction.trigger() visible: reportBugAction.text !== "" } MenuSeparator { visible: reportBugAction.text !== "" } + 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: configureShortcutsAction.text !== "" + visible: configureAction.text !== "" || configureShortcutsAction.text !== "" } MenuItem { text: aboutAppAction.text shortcut: aboutAppAction.shortcut iconName: elisa.iconName(aboutAppAction.icon) onTriggered: aboutAppAction.trigger() visible: aboutAppAction.text !== "" } } Action { id: applicationMenuAction text: i18nc("open application menu", "Application Menu") iconName: "application-menu" onTriggered: applicationMenu.popup() } 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 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: playListControlerItem.skipPreviousTrack() playerControl.onPlayNext: playListControlerItem.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 } } } RowLayout { Layout.fillHeight: true Layout.fillWidth: true spacing: 0 Rectangle { color: myPalette.window Layout.fillHeight: true Layout.preferredWidth: mainWindow.width * 0.15 Layout.maximumWidth: mainWindow.width * 0.15 ScrollView { flickableItem.boundsBehavior: Flickable.StopAtBounds anchors.fill: parent ListView { id: viewModeView focus: true z: 2 model: DelegateModel { id: pageDelegateModel groups: [ DelegateModelGroup { name: "selected" } ] model: ListModel { id: pageModel } delegate: Rectangle { id: item height: elisaTheme.viewSelectorDelegateHeight width: viewModeView.width color: (DelegateModel.inSelected ? myPalette.highlight : myPalette.window) MouseArea { anchors.fill: parent acceptedButtons: Qt.LeftButton LabelWithToolTip { id: nameLabel anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.right: parent.right anchors.leftMargin: elisaTheme.layoutHorizontalMargin anchors.rightMargin: elisaTheme.layoutHorizontalMargin verticalAlignment: "AlignVCenter" text: model.name color: (item.DelegateModel.inSelected ? myPalette.highlightedText : myPalette.text) } onClicked: { var myGroup = pageDelegateModel.groups[2] if (myGroup.count > 0 && !item.DelegateModel.inSelected) { myGroup.remove(0, myGroup.count) } item.DelegateModel.inSelected = !item.DelegateModel.inSelected if (item.DelegateModel.inSelected) { viewModeView.currentIndex = index } } } } Component.onCompleted: { pageModel.insert(0, {"name": i18nc("Title of the view of the playlist", "Now Playing")}) pageModel.insert(1, {"name": i18nc("Title of the view of all albums", "Albums")}) pageModel.insert(2, {"name": i18nc("Title of the view of all artists", "Artists")}) pageModel.insert(3, {"name": i18nc("Title of the view of all tracks", "Tracks")}) items.get(1).inSelected = 1 viewModeView.currentIndex = 1 } } } } } Item { Layout.fillHeight: true Layout.fillWidth: true RowLayout { anchors.fill: parent spacing: 0 id: contentZone Item { id: mainContentView Layout.leftMargin: elisaTheme.layoutHorizontalMargin Layout.rightMargin: elisaTheme.layoutHorizontalMargin Layout.fillHeight: true Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 //z: 1 visible: Layout.minimumWidth != 0 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 } MediaBrowser { id: localAlbums anchors.fill: parent firstPage: MediaAllAlbumView { playListModel: playListModelItem playerControl: manageAudioPlayer stackView: localAlbums.stackView musicListener: allListeners contentDirectoryModel: allAlbumsModel } visible: opacity > 0 } MediaBrowser { id: localArtists anchors.fill: parent firstPage: MediaAllArtistView { playListModel: playListModelItem artistsModel: allArtistsModel playerControl: manageAudioPlayer stackView: localArtists.stackView musicListener: allListeners contentDirectoryModel: allAlbumsModel } visible: opacity > 0 } MediaBrowser { id: localTracks anchors.fill: parent firstPage: MediaAllTracksView { playListModel: playListModelItem tracksModel: allTracksModel playerControl: manageAudioPlayer stackView: localTracks.stackView musicListener: allListeners } visible: opacity > 0 } } 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: playListControlerItem randomPlayChecked: playListControlerItem.randomPlayControl repeatPlayChecked: playListControlerItem.repeatPlayControl Layout.fillHeight: true Layout.minimumWidth: contentZone.width Layout.maximumWidth: contentZone.width Layout.preferredWidth: contentZone.width Component.onCompleted: { playListControlerItem.randomPlay = Qt.binding(function() { return playList.randomPlayChecked }) playListControlerItem.repeatPlay = Qt.binding(function() { return playList.repeatPlayChecked }) myPlayControlManager.randomOrContinuePlay = Qt.binding(function() { return playList.randomPlayChecked || playList.repeatPlayChecked }) } } 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: viewModeView.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: viewModeView.currentIndex === 1 PropertyChanges { target: mainContentView Layout.fillWidth: true Layout.minimumWidth: contentZone.width * 0.64 Layout.maximumWidth: contentZone.width * 0.66 } 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: viewModeView.currentIndex === 2 PropertyChanges { target: mainContentView Layout.fillWidth: true Layout.minimumWidth: contentZone.width * 0.64 Layout.maximumWidth: contentZone.width * 0.66 } 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: viewModeView.currentIndex === 3 PropertyChanges { target: mainContentView Layout.fillWidth: true Layout.minimumWidth: contentZone.width * 0.64 Layout.maximumWidth: contentZone.width * 0.66 } 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 } } } } } } } diff --git a/src/abstractfile/abstractfilelistener.cpp b/src/abstractfile/abstractfilelistener.cpp index a4cc4b5a..132c5ef8 100644 --- a/src/abstractfile/abstractfilelistener.cpp +++ b/src/abstractfile/abstractfilelistener.cpp @@ -1,87 +1,99 @@ /* * 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 "abstractfilelistener.h" #include "abstractfilelisting.h" #include "databaseinterface.h" #include class AbstractFileListenerPrivate { public: - explicit AbstractFileListenerPrivate(AbstractFileListing *aFileListing) : mFileListing(aFileListing) + explicit AbstractFileListenerPrivate() { } QThread mFileQueryThread; - AbstractFileListing *mFileListing; + AbstractFileListing *mFileListing = nullptr; }; -AbstractFileListener::AbstractFileListener(AbstractFileListing *aFileListing, QObject *parent) : QObject(parent), d(new AbstractFileListenerPrivate(aFileListing)) +AbstractFileListener::AbstractFileListener(QObject *parent) + : QObject(parent), d(new AbstractFileListenerPrivate) { - d->mFileQueryThread.start(); - d->mFileListing->moveToThread(&d->mFileQueryThread); } AbstractFileListener::~AbstractFileListener() { - delete d->mFileListing; delete d; } DatabaseInterface *AbstractFileListener::databaseInterface() const { return nullptr; } void AbstractFileListener::setDatabaseInterface(DatabaseInterface *model) { if (model) { connect(this, &AbstractFileListener::databaseReady, d->mFileListing, &AbstractFileListing::databaseIsReady); connect(this, &AbstractFileListener::newTrackFile, d->mFileListing, &AbstractFileListing::newTrackFile); connect(d->mFileListing, &AbstractFileListing::tracksList, model, &DatabaseInterface::insertTracksList); connect(d->mFileListing, &AbstractFileListing::removedTracksList, model, &DatabaseInterface::removeTracksList); connect(d->mFileListing, &AbstractFileListing::modifyTracksList, model, &DatabaseInterface::modifyTracksList); QMetaObject::invokeMethod(d->mFileListing, "init", Qt::QueuedConnection); } Q_EMIT databaseInterfaceChanged(); } void AbstractFileListener::applicationAboutToQuit() { d->mFileListing->applicationAboutToQuit(); d->mFileQueryThread.exit(); d->mFileQueryThread.wait(); } +void AbstractFileListener::setFileListing(AbstractFileListing *fileIndexer) +{ + d->mFileListing = fileIndexer; + d->mFileQueryThread.start(); + d->mFileListing->moveToThread(&d->mFileQueryThread); + connect(fileIndexer, &AbstractFileListing::indexingFinished, + this, &AbstractFileListener::indexingFinished); +} + AbstractFileListing *AbstractFileListener::fileListing() const { return d->mFileListing; } +void AbstractFileListener::performInitialScan() +{ + d->mFileListing->refreshContent(); +} + #include "moc_abstractfilelistener.cpp" diff --git a/src/abstractfile/abstractfilelistener.h b/src/abstractfile/abstractfilelistener.h index d0330845..aa34a24f 100644 --- a/src/abstractfile/abstractfilelistener.h +++ b/src/abstractfile/abstractfilelistener.h @@ -1,72 +1,80 @@ /* * 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 #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) public: - explicit AbstractFileListener(AbstractFileListing *aFileListing, QObject *parent = 0); + explicit AbstractFileListener(QObject *parent = 0); virtual ~AbstractFileListener(); DatabaseInterface* databaseInterface() const; + AbstractFileListing* fileListing() const; + Q_SIGNALS: void databaseInterfaceChanged(); void databaseReady(); void newTrackFile(const MusicAudioTrack &newTrack); + void indexingFinished(); + + void configurationChanged(); + public Q_SLOTS: + void performInitialScan(); + void setDatabaseInterface(DatabaseInterface* databaseInterface); void applicationAboutToQuit(); protected: - AbstractFileListing* fileListing() const; + void setFileListing(AbstractFileListing *fileIndexer); private: AbstractFileListenerPrivate *d = nullptr; }; #endif // ABSTRACTFILELISTENER_H diff --git a/src/abstractfile/abstractfilelisting.cpp b/src/abstractfile/abstractfilelisting.cpp index e0ee14f9..d768edc2 100644 --- a/src/abstractfile/abstractfilelisting.cpp +++ b/src/abstractfile/abstractfilelisting.cpp @@ -1,432 +1,437 @@ /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class AbstractFileListingPrivate { public: explicit AbstractFileListingPrivate(const QString &sourceName) : mSourceName(sourceName) { } QFileSystemWatcher mFileSystemWatcher; QHash mAllAlbumCover; QHash>> mDiscoveredFiles; QString mSourceName; bool mHandleNewFiles = true; KFileMetaData::ExtractorCollection mExtractors; QAtomicInt mStopRequest = 0; QMimeDatabase mMimeDb; }; AbstractFileListing::AbstractFileListing(const QString &sourceName, QObject *parent) : QObject(parent), d(new AbstractFileListingPrivate(sourceName)) { connect(&d->mFileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, &AbstractFileListing::directoryChanged); connect(&d->mFileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &AbstractFileListing::fileChanged); } AbstractFileListing::~AbstractFileListing() { } void AbstractFileListing::init() { executeInit(); } void AbstractFileListing::databaseIsReady() { refreshContent(); } void AbstractFileListing::newTrackFile(const MusicAudioTrack &partialTrack) { const auto &newTrack = scanOneFile(partialTrack.resourceURI()); if (newTrack.isValid() && newTrack != partialTrack) { Q_EMIT modifyTracksList({newTrack}, d->mAllAlbumCover); } } void AbstractFileListing::applicationAboutToQuit() { d->mStopRequest = 1; } void AbstractFileListing::scanDirectory(QList &newFiles, const QUrl &path) { 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); continue; } if (!oneEntry.isFile()) { continue; } auto newTrack = scanOneFile(newFilePath); if (newTrack.isValid()) { addCover(newTrack); addFileInDirectory(newTrack.resourceURI(), path); newFiles.push_back(newTrack); } if (d->mStopRequest == 1) { break; } } } const QString &AbstractFileListing::sourceName() const { return d->mSourceName; } void AbstractFileListing::directoryChanged(const QString &path) { const auto directoryEntry = d->mDiscoveredFiles.find(QUrl::fromLocalFile(path)); if (directoryEntry == d->mDiscoveredFiles.end()) { return; } scanDirectoryTree(path); } void AbstractFileListing::fileChanged(const QString &modifiedFileName) { auto modifiedFile = QUrl::fromLocalFile(modifiedFileName); auto modifiedTrack = scanOneFile(modifiedFile); if (modifiedTrack.isValid()) { Q_EMIT modifyTracksList({modifiedTrack}, d->mAllAlbumCover); } } void AbstractFileListing::executeInit() { } void AbstractFileListing::triggerRefreshOfContent() { } void AbstractFileListing::refreshContent() { triggerRefreshOfContent(); } MusicAudioTrack AbstractFileListing::scanOneFile(const QUrl &scanFile) { MusicAudioTrack newTrack; const auto &fileMimeType = d->mMimeDb.mimeTypeForFile(scanFile.toLocalFile()); if (!fileMimeType.name().startsWith(QStringLiteral("audio/"))) { return newTrack; } QString mimetype = fileMimeType.name(); QList exList = d->mExtractors.fetchExtractors(mimetype); if (exList.isEmpty()) { return newTrack; } QFileInfo scanFileInfo(scanFile.toLocalFile()); if (scanFileInfo.exists()) { watchPath(scanFile.toLocalFile()); } KFileMetaData::Extractor* ex = exList.first(); KFileMetaData::SimpleExtractionResult result(scanFile.toLocalFile(), mimetype, KFileMetaData::ExtractionResult::ExtractMetaData); ex->extract(&result); const auto &allProperties = result.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); #if defined Q_OS_LINUX && !defined Q_OS_ANDROID auto fileData = KFileMetaData::UserMetaData(scanFile.toLocalFile()); #endif if (albumProperty != allProperties.end()) { auto albumValue = albumProperty->toString(); 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()); } if (albumArtistProperty != allProperties.end()) { newTrack.setAlbumArtist(albumArtistProperty->toString()); } if (newTrack.artist().isEmpty()) { newTrack.setArtist(newTrack.albumArtist()); } newTrack.setResourceURI(scanFile); #if defined Q_OS_LINUX && !defined Q_OS_ANDROID newTrack.setRating(fileData.rating()); #endif 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); } 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) { emitNewFiles(newFiles); } } void AbstractFileListing::setHandleNewFiles(bool handleThem) { d->mHandleNewFiles = handleThem; } void AbstractFileListing::emitNewFiles(const QList &tracks) { 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.albumName()] = 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; +} + #include "moc_abstractfilelisting.cpp" diff --git a/src/abstractfile/abstractfilelisting.h b/src/abstractfile/abstractfilelisting.h index 9ba4344e..4b2561af 100644 --- a/src/abstractfile/abstractfilelisting.h +++ b/src/abstractfile/abstractfilelisting.h @@ -1,107 +1,111 @@ /* * 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 #include #include #include #include #include class AbstractFileListingPrivate; class MusicAudioTrack; class AbstractFileListing : public QObject { Q_OBJECT public: explicit AbstractFileListing(const QString &sourceName, QObject *parent = 0); virtual ~AbstractFileListing(); virtual void applicationAboutToQuit(); + const QString &sourceName() 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); + void indexingFinished(); + public Q_SLOTS: void refreshContent(); void init(); void databaseIsReady(); void newTrackFile(const MusicAudioTrack &partialTrack); 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); - const QString &sourceName() const; - 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); + private: std::unique_ptr d; }; #endif // ABSTRACTFILELISTING_H diff --git a/src/allalbumsmodel.cpp b/src/allalbumsmodel.cpp index 39e4fbca..fc28181a 100644 --- a/src/allalbumsmodel.cpp +++ b/src/allalbumsmodel.cpp @@ -1,252 +1,257 @@ /* * 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 #include #include #include #include class AllAlbumsModelPrivate { public: AllAlbumsModelPrivate() { } QVector mAllAlbums; int mAlbumCount = 0; }; AllAlbumsModel::AllAlbumsModel(QObject *parent) : QAbstractItemModel(parent), d(new AllAlbumsModelPrivate) { } AllAlbumsModel::~AllAlbumsModel() { delete d; } +int AllAlbumsModel::albumCount() const +{ + return rowCount({}); +} + int AllAlbumsModel::rowCount(const QModelIndex &parent) const { auto albumCount = 0; if (parent.isValid()) { return albumCount; } albumCount = d->mAlbumCount; return albumCount; } QHash AllAlbumsModel::roleNames() const { QHash roles; 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::AlbumDataRole)] = "albumData"; roles[static_cast(ColumnsRoles::HighestTrackRating)] = "highestTrackRating"; roles[static_cast(ColumnsRoles::AlbumDatabaseIdRole)] = "albumId"; 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; 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(); ColumnsRoles convertedRole = static_cast(role); switch(convertedRole) { case ColumnsRoles::TitleRole: result = d->mAllAlbums[albumIndex].title(); break; case ColumnsRoles::AllTracksTitleRole: result = d->mAllAlbums[albumIndex].allTracksTitle(); break; case ColumnsRoles::ArtistRole: result = d->mAllAlbums[albumIndex].artist(); break; case ColumnsRoles::AllArtistsRole: result = d->mAllAlbums[albumIndex].allArtists().join(QStringLiteral(", ")); break; case ColumnsRoles::ImageRole: { auto albumArt = d->mAllAlbums[albumIndex].albumArtURI(); if (albumArt.isValid()) { result = albumArt; } break; } case ColumnsRoles::CountRole: result = d->mAllAlbums[albumIndex].tracksCount(); break; case ColumnsRoles::IdRole: result = d->mAllAlbums[albumIndex].id(); break; case ColumnsRoles::IsSingleDiscAlbumRole: result = d->mAllAlbums[albumIndex].isSingleDiscAlbum(); break; case ColumnsRoles::AlbumDataRole: result = QVariant::fromValue(d->mAllAlbums[albumIndex]); break; case ColumnsRoles::AlbumDatabaseIdRole: result = QVariant::fromValue(d->mAllAlbums[albumIndex].databaseId()); break; case ColumnsRoles::HighestTrackRating: result = d->mAllAlbums[albumIndex].highestTrackRating(); 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; } void AllAlbumsModel::albumAdded(const MusicAlbum &newAlbum) { if (newAlbum.isValid()) { beginInsertRows({}, d->mAllAlbums.size(), d->mAllAlbums.size()); d->mAllAlbums.push_back(newAlbum); ++d->mAlbumCount; endInsertRows(); } } void AllAlbumsModel::albumRemoved(const MusicAlbum &removedAlbum) { auto removedAlbumIterator = std::find(d->mAllAlbums.begin(), d->mAllAlbums.end(), removedAlbum); if (removedAlbumIterator == d->mAllAlbums.end()) { return; } int albumIndex = removedAlbumIterator - d->mAllAlbums.begin(); beginRemoveRows({}, albumIndex, albumIndex); d->mAllAlbums.erase(removedAlbumIterator); --d->mAlbumCount; endRemoveRows(); } void AllAlbumsModel::albumModified(const MusicAlbum &modifiedAlbum) { auto modifiedAlbumIterator = std::find(d->mAllAlbums.begin(), d->mAllAlbums.end(), modifiedAlbum); if (modifiedAlbumIterator == d->mAllAlbums.end()) { return; } int albumIndex = modifiedAlbumIterator - d->mAllAlbums.begin(); d->mAllAlbums[albumIndex] = modifiedAlbum; Q_EMIT dataChanged(index(albumIndex, 0), index(albumIndex, 0)); } #include "moc_allalbumsmodel.cpp" diff --git a/src/allalbumsmodel.h b/src/allalbumsmodel.h index 62f2a42c..c29e13b4 100644 --- a/src/allalbumsmodel.h +++ b/src/allalbumsmodel.h @@ -1,91 +1,93 @@ /* * 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. */ #ifndef ALLALBUMSMODEL_H #define ALLALBUMSMODEL_H #include #include #include #include #include "musicalbum.h" #include "musicaudiotrack.h" class AllAlbumsModelPrivate; class MusicStatistics; class QMutex; class AllAlbumsModel : public QAbstractItemModel { Q_OBJECT public: enum ColumnsRoles { TitleRole = Qt::UserRole + 1, AllTracksTitleRole = TitleRole + 1, ArtistRole = AllTracksTitleRole + 1, AllArtistsRole = ArtistRole + 1, ImageRole = AllArtistsRole + 1, CountRole = ImageRole + 1, IdRole = CountRole + 1, IsSingleDiscAlbumRole = IdRole + 1, AlbumDataRole = IsSingleDiscAlbumRole + 1, HighestTrackRating = AlbumDataRole + 1, AlbumDatabaseIdRole = HighestTrackRating + 1, }; Q_ENUM(ColumnsRoles) explicit AllAlbumsModel(QObject *parent = 0); virtual ~AllAlbumsModel(); + Q_INVOKABLE int albumCount() const; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; QHash roleNames() const override; Qt::ItemFlags flags(const QModelIndex &index) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &child) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; public Q_SLOTS: void albumAdded(const MusicAlbum &newAlbum); void albumRemoved(const MusicAlbum &removedAlbum); void albumModified(const MusicAlbum &modifiedAlbum); private: QVariant internalDataAlbum(int albumIndex, int role) const; AllAlbumsModelPrivate *d; }; #endif // ALLALBUMSMODEL_H diff --git a/src/baloo/baloolistener.cpp b/src/baloo/baloolistener.cpp index be8d8782..4d54bdd8 100644 --- a/src/baloo/baloolistener.cpp +++ b/src/baloo/baloolistener.cpp @@ -1,43 +1,46 @@ /* * Copyright 2016 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 "baloolistener.h" #include "localbaloofilelisting.h" #include "databaseinterface.h" #include class BalooListenerPrivate { public: + LocalBalooFileListing mBalooFileIndexer; + }; -BalooListener::BalooListener(QObject *parent) : AbstractFileListener(new LocalBalooFileListing, parent), d(new BalooListenerPrivate) +BalooListener::BalooListener(QObject *parent) : AbstractFileListener(parent), d(new BalooListenerPrivate) { + setFileListing(&d->mBalooFileIndexer); } BalooListener::~BalooListener() { delete d; } #include "moc_baloolistener.cpp" diff --git a/src/baloo/localbaloofilelisting.cpp b/src/baloo/localbaloofilelisting.cpp index 92882388..a93d429c 100644 --- a/src/baloo/localbaloofilelisting.cpp +++ b/src/baloo/localbaloofilelisting.cpp @@ -1,242 +1,244 @@ /* * 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 #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; }; LocalBalooFileListing::LocalBalooFileListing(QObject *parent) : AbstractFileListing(QStringLiteral("baloo"), parent), d(new LocalBalooFileListingPrivate) { d->mQuery.addType(QStringLiteral("Audio")); setHandleNewFiles(false); } LocalBalooFileListing::~LocalBalooFileListing() { } void LocalBalooFileListing::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::executeInit() { auto sessionBus = QDBusConnection::sessionBus(); auto methodCall = QDBusMessage::createMethodCall(QStringLiteral("org.kde.baloo"), QStringLiteral("/fileindexer"), QStringLiteral("org.kde.baloo.fileindexer"), QStringLiteral("registerMonitor")); auto answer = sessionBus.call(methodCall); if (answer.type() != QDBusMessage::ReplyMessage) { qDebug() << "LocalBalooFileListing::executeInit" << answer.errorName() << answer.errorMessage(); } sessionBus.connect(QStringLiteral("org.kde.baloo"), QStringLiteral("/fileindexer"), QStringLiteral("org.kde.baloo.fileindexer"), QStringLiteral("finishedIndexingFile"), this, SLOT(newBalooFile(QString))); } void LocalBalooFileListing::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); } } if (!newFiles.isEmpty() && d->mStopRequest == 0) { emitNewFiles(newFiles); } + + Q_EMIT indexingFinished(); } 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 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()); } if (albumArtistProperty != allProperties.end()) { newTrack.setAlbumArtist(albumArtistProperty->toString()); } if (newTrack.artist().isEmpty()) { newTrack.setArtist(newTrack.albumArtist()); } 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()); } 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; } #include "moc_localbaloofilelisting.cpp" diff --git a/src/databaseinterface.cpp b/src/databaseinterface.cpp index ab19e503..45f738f9 100644 --- a/src/databaseinterface.cpp +++ b/src/databaseinterface.cpp @@ -1,2586 +1,2693 @@ /* * 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), mSelectTrackIdFromTitleAlbumArtistQuery(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), mUpdateAlbumArtistFromAlbumIdQuery(mTracksDatabase), - mInsertAlbumWithoutArtistQuery(mTracksDatabase), mSelectTracksMappingPriorityByTrackId(mTracksDatabase) + mInsertAlbumWithoutArtistQuery(mTracksDatabase), mSelectTracksMappingPriorityByTrackId(mTracksDatabase), + mSelectAllTrackFilesFromSourceQuery(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 mSelectTrackIdFromTitleAlbumArtistQuery; 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 mUpdateAlbumArtistFromAlbumIdQuery; QSqlQuery mInsertAlbumWithoutArtistQuery; QSqlQuery mSelectTracksMappingPriorityByTrackId; + QSqlQuery mSelectAllTrackFilesFromSourceQuery; + 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(); } delete d; } 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 = new DatabaseInterfacePrivate(tracksDatabase); initDatabase(); initRequest(); if (!databaseFileName.isEmpty()) { reloadExistingDatabase(); } } MusicAlbum DatabaseInterface::albumFromTitle(const QString &title) { auto result = MusicAlbum(); auto transactionResult = startTransaction(); if (!transactionResult) { return result; } result = internalAlbumFromTitle(title); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } QList DatabaseInterface::allTracks() const { 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()) { 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) const { 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()) { 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) const { 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()) { 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()) { 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() const { 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()) { 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()) { 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) const { 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) const { 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()) { 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()) { 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::trackIdFromTitleAlbumArtist(const QString &title, const QString &album, const QString &artist) const { auto result = qulonglong(0); if (!d) { return result; } auto transactionResult = startTransaction(); if (!transactionResult) { return result; } result = internalTrackIdFromTitleAlbumArtist(title, album, artist); 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()) { + 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()) { + 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); + + 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()) { 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); 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; } - QList willRemoveTrack; - - for (const auto &removedTrackFileName : removedTracks) { - d->mSelectTrackFromFilePathQuery.bindValue(QStringLiteral(":filePath"), removedTrackFileName.toString()); - - auto result = d->mSelectTrackFromFilePathQuery.exec(); - - if (!result || !d->mSelectTrackFromFilePathQuery.isSelect() || !d->mSelectTrackFromFilePathQuery.isActive()) { - qDebug() << "DatabaseInterface::removeTracksList" << d->mSelectTrackFromFilePathQuery.lastQuery(); - qDebug() << "DatabaseInterface::removeTracksList" << d->mSelectTrackFromFilePathQuery.boundValues(); - qDebug() << "DatabaseInterface::removeTracksList" << d->mSelectTrackFromFilePathQuery.lastError(); - - continue; - } - - while (d->mSelectTrackFromFilePathQuery.next()) { - const auto ¤tRecord = d->mSelectTrackFromFilePathQuery.record(); - - willRemoveTrack.push_back(buildTrackFromDatabaseRecord(currentRecord)); - } - - d->mSelectTrackFromFilePathQuery.finish(); - } - - QSet modifiedAlbums; - - for (const auto &oneRemovedTrack : willRemoveTrack) { - removeTrackInDatabase(oneRemovedTrack.databaseId()); - Q_EMIT trackRemoved(oneRemovedTrack.databaseId()); - - const auto &modifiedAlbumId = internalAlbumIdFromTitle(oneRemovedTrack.albumName()); - const auto &allArtistTracks = internalTracksFromAuthor(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 (allArtistTracks.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); - } - } + internalRemoveTracksList(removedTracks); transactionResult = finishTransaction(); if (!transactionResult) { return; } } void DatabaseInterface::modifyTracksList(const QList &modifiedTracks, const QHash &covers) { auto transactionResult = startTransaction(); if (!transactionResult) { return; } for (const auto &oneModifiedTrack : modifiedTracks) { if (oneModifiedTrack.albumArtist().isEmpty()) { continue; } auto originTrackId = internalTrackIdFromFileName(oneModifiedTrack.resourceURI()); auto originTrack = MusicAudioTrack(); if (originTrackId != 0) { originTrack = internalTrackFromDatabaseId(originTrackId); } if (originTrack.isValid() && originTrack == oneModifiedTrack) { continue; } auto albumId = insertAlbum(oneModifiedTrack.albumName(), (oneModifiedTrack.isValidAlbumArtist() ? oneModifiedTrack.albumArtist() : QString()), covers[oneModifiedTrack.albumName()], 0, true); if (albumId == 0) { continue; } auto otherTrackId = internalTrackIdFromTitleAlbumArtist(oneModifiedTrack.title(), oneModifiedTrack.albumName(), oneModifiedTrack.artist()); if (originTrack.isValid() || otherTrackId != 0) { if (otherTrackId != 0) { originTrackId = otherTrackId; } const auto oldTrack = internalTrackFromDatabaseId(originTrackId); if (oldTrack == oneModifiedTrack) { updateTrackOrigin(originTrackId, oneModifiedTrack.resourceURI()); continue; } removeTrackInDatabase(originTrackId); } else { originTrackId = d->mTrackId; } d->mInsertTrackQuery.bindValue(QStringLiteral(":trackId"), originTrackId); d->mInsertTrackQuery.bindValue(QStringLiteral(":title"), oneModifiedTrack.title()); d->mInsertTrackQuery.bindValue(QStringLiteral(":album"), albumId); d->mInsertTrackQuery.bindValue(QStringLiteral(":artistId"), insertArtist(oneModifiedTrack.artist())); d->mInsertTrackQuery.bindValue(QStringLiteral(":trackNumber"), oneModifiedTrack.trackNumber()); d->mInsertTrackQuery.bindValue(QStringLiteral(":discNumber"), oneModifiedTrack.discNumber()); d->mInsertTrackQuery.bindValue(QStringLiteral(":trackDuration"), QVariant::fromValue(oneModifiedTrack.duration().msecsSinceStartOfDay())); d->mInsertTrackQuery.bindValue(QStringLiteral(":trackRating"), oneModifiedTrack.rating()); auto result = d->mInsertTrackQuery.exec(); if (result && d->mInsertTrackQuery.isActive()) { d->mInsertTrackQuery.finish(); if (!originTrack.isValid()) { ++d->mTrackId; } updateTrackOrigin(originTrackId, oneModifiedTrack.resourceURI()); if (originTrack.isValid() || otherTrackId != 0) { Q_EMIT trackModified(internalTrackFromDatabaseId(originTrackId)); Q_EMIT albumModified(internalAlbumFromId(albumId), albumId); } else { Q_EMIT trackAdded(originTrackId); } updateAlbumFromId(albumId, oneModifiedTrack.albumCover(), oneModifiedTrack); if (updateTracksCount(albumId)) { Q_EMIT albumModified(internalAlbumFromId(albumId), albumId); } } else { d->mInsertTrackQuery.finish(); qDebug() << "DatabaseInterface::modifyTracksList" << d->mInsertTrackQuery.lastQuery(); qDebug() << "DatabaseInterface::modifyTracksList" << d->mInsertTrackQuery.boundValues(); qDebug() << "DatabaseInterface::modifyTracksList" << d->mInsertTrackQuery.lastError(); continue; } } 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; } const auto &listTables = d->mTracksDatabase.tables(); 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.lastError() << createSchemaQuery.lastError().nativeErrorCode(); } } 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, " "`ArtistID` INTEGER, " "`CoverFileName` VARCHAR(255) NOT NULL, " "`TracksCount` INTEGER NOT NULL, " "`IsSingleDiscAlbum` BOOLEAN NOT NULL, " "`AlbumInternalID` VARCHAR(55), " "UNIQUE (`Title`, `ArtistID`), " "CONSTRAINT fk_albums_artist FOREIGN KEY (`ArtistID`) REFERENCES `Artists`(`ID`))")); if (!result) { 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, " "`ArtistID` INTEGER NOT NULL, " "`TrackNumber` INTEGER NOT NULL, " "`DiscNumber` INTEGER, " "`Duration` INTEGER NOT NULL, " "`Rating` INTEGER NOT NULL DEFAULT 0, " "UNIQUE (`Title`, `AlbumID`, `ArtistID`), " "CONSTRAINT fk_tracks_album FOREIGN KEY (`AlbumID`) REFERENCES `Albums`(`ID`), " "CONSTRAINT fk_tracks_artist FOREIGN KEY (`ArtistID`) REFERENCES `Artists`(`ID`))")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastError(); } } else { auto listColumns = d->mTracksDatabase.record(QStringLiteral("Tracks")); if (!listColumns.contains(QStringLiteral("Rating"))) { QSqlQuery alterSchemaQuery(d->mTracksDatabase); const auto &result = alterSchemaQuery.exec(QStringLiteral("ALTER TABLE `Tracks` " "ADD COLUMN RATING INTEGER NOT NULL DEFAULT 0")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << alterSchemaQuery.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.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.lastError(); } } { QSqlQuery createTrackIndex(d->mTracksDatabase); const auto &result = createTrackIndex.exec(QStringLiteral("CREATE INDEX " "IF NOT EXISTS " "`AlbumsArtistIndex` ON `Albums` " "(`ArtistID`)")); if (!result) { 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.lastError(); } } { QSqlQuery createTrackIndex(d->mTracksDatabase); const auto &result = createTrackIndex.exec(QStringLiteral("CREATE INDEX " "IF NOT EXISTS " "`TracksArtistIDAlbumIDFileNameIndex` ON `Tracks` " "(`ArtistID`, `AlbumID`)")); if (!result) { 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 `Artists` artist " "ON " "artist.`ID` = album.`ArtistID` " "WHERE " "album.`ID` = :albumId"); auto result = d->mSelectAlbumQuery.prepare(selectAlbumQueryText); if (!result) { 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 `Artists` artist " "ON " "artist.`ID` = album.`ArtistID` " "ORDER BY album.`Title`"); auto result = d->mSelectAllAlbumsQuery.prepare(selectAllAlbumsText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << selectAllAlbumsText << d->mSelectAllAlbumsQuery.lastError(); qDebug() << d->mTracksDatabase.lastError(); } } { auto selectAllArtistsWithFilterText = QStringLiteral("SELECT `ID`, " "`Name` " "FROM `Artists`"); auto result = d->mSelectAllArtistsQuery.prepare(selectAllArtistsWithFilterText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << selectAllArtistsWithFilterText << d->mSelectAllArtistsQuery.lastError(); qDebug() << d->mTracksDatabase.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` " "FROM `Tracks` tracks, `Artists` artist, `Albums` album, `TracksMapping` tracksMapping " "LEFT JOIN `Artists` artistAlbum ON artistAlbum.`ID` = album.`ArtistID` " "WHERE " "artist.`ID` = tracks.`ArtistID` AND " "tracks.`AlbumID` = album.`ID` AND " "tracksMapping.`TrackID` = tracks.`ID` AND " "tracksMapping.`Priority` = 1"); auto result = d->mSelectAllTracksQuery.prepare(selectAllTracksText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << selectAllTracksText << d->mSelectAllTracksQuery.lastError(); qDebug() << d->mTracksDatabase.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` " "FROM `Tracks` tracks, `Artists` artist, " "`Albums` album , `TracksMapping` tracksMapping, `DiscoverSource` source " "LEFT JOIN `Artists` artistAlbum ON artistAlbum.`ID` = album.`ArtistID` " "WHERE " "artist.`ID` = tracks.`ArtistID` AND " "tracks.`AlbumID` = album.`ID` AND " "source.`Name` = :source AND " "source.`ID` = tracksMapping.`DiscoverID` AND " "tracksMapping.`TrackValid` = 0 AND " "tracksMapping.`TrackID` = tracks.`ID` AND " "tracksMapping.`Priority` = 1"); auto result = d->mSelectAllInvalidTracksFromSourceQuery.prepare(selectAllInvalidTracksFromSourceQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << selectAllInvalidTracksFromSourceQueryText << d->mSelectAllInvalidTracksFromSourceQuery.lastError(); qDebug() << d->mTracksDatabase.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` " "FROM `Tracks` tracks, `Artists` artist, " "`Albums` album , `TracksMapping` tracksMapping, `DiscoverSource` source " "LEFT JOIN `Artists` artistAlbum ON artistAlbum.`ID` = album.`ArtistID` " "WHERE " "artist.`ID` = tracks.`ArtistID` AND " "tracks.`AlbumID` = album.`ID` AND " "source.`Name` = :source AND " "source.`ID` = tracksMapping.`DiscoverID` AND " "tracksMapping.`TrackID` = tracks.`ID` AND " "tracksMapping.`Priority` = 1"); auto result = d->mSelectAllTracksFromSourceQuery.prepare(selectAllTracksFromSourceQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << selectAllTracksFromSourceQueryText << d->mSelectAllTracksFromSourceQuery.lastError(); qDebug() << d->mTracksDatabase.lastError(); } } { auto selectArtistByNameText = QStringLiteral("SELECT `ID`, " "`Name` " "FROM `Artists` " "WHERE " "`Name` = :name"); auto result = d->mSelectArtistByNameQuery.prepare(selectArtistByNameText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << selectArtistByNameText << d->mSelectArtistByNameQuery.lastError(); qDebug() << d->mTracksDatabase.lastError(); } } { auto insertArtistsText = QStringLiteral("INSERT INTO `Artists` (`ID`, `Name`) " "VALUES (:artistId, :name)"); auto result = d->mInsertArtistsQuery.prepare(insertArtistsText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << insertArtistsText << d->mInsertArtistsQuery.lastError(); qDebug() << d->mTracksDatabase.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` " "FROM `Tracks` tracks, `Artists` artist, `Albums` album, `TracksMapping` tracksMapping " "LEFT JOIN `Artists` artistAlbum ON artistAlbum.`ID` = album.`ArtistID` " "WHERE " "tracksMapping.`TrackID` = tracks.`ID` AND " "tracks.`AlbumID` = :albumId AND " "artist.`ID` = tracks.`ArtistID` AND " "album.`ID` = :albumId AND " "tracksMapping.`Priority` = 1 " "ORDER BY tracks.`DiscNumber` ASC, " "tracks.`TrackNumber` ASC"); auto result = d->mSelectTrackQuery.prepare(selectTrackQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackQuery.lastError(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackQuery.lastQuery(); } } { 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` " "FROM `Tracks` tracks, `Artists` artist, `Albums` album, `TracksMapping` tracksMapping " "LEFT JOIN `Artists` artistAlbum ON artistAlbum.`ID` = album.`ArtistID` " "WHERE " "tracks.`ID` = :trackId AND " "artist.`ID` = tracks.`ArtistID` AND " "tracks.`AlbumID` = album.`ID` AND " "tracksMapping.`TrackID` = tracks.`ID` AND " "tracksMapping.`Priority` = 1"); auto result = d->mSelectTrackFromIdQuery.prepare(selectTrackFromIdQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackFromIdQuery.lastError(); } } { auto selectCountAlbumsQueryText = QStringLiteral("SELECT count(*) " "FROM `Albums` album, `Artists` artist " "WHERE artist.`Name` = :artistName AND " "artist.`ID` = album.`ArtistID`"); const auto result = d->mSelectCountAlbumsForArtistQuery.prepare(selectCountAlbumsQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectCountAlbumsForArtistQuery.lastError(); } } { auto selectAlbumIdFromTitleQueryText = QStringLiteral("SELECT `ID` FROM `Albums` " "WHERE " "`Title` = :title"); auto result = d->mSelectAlbumIdFromTitleQuery.prepare(selectAlbumIdFromTitleQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumIdFromTitleQuery.lastError(); } } { auto insertAlbumQueryText = QStringLiteral("INSERT INTO Albums (`ID`, `Title`, `ArtistID`, `CoverFileName`, `TracksCount`, `IsSingleDiscAlbum`) " "VALUES (:albumId, :title, :artistId, :coverFileName, :tracksCount, :isSingleDiscAlbum)"); auto result = d->mInsertAlbumQuery.prepare(insertAlbumQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mInsertAlbumQuery.lastError(); } } { auto insertAlbumWithoutArtistQueryText = QStringLiteral("INSERT INTO Albums (`ID`, `Title`, `CoverFileName`, `TracksCount`, `IsSingleDiscAlbum`) " "VALUES (:albumId, :title, :coverFileName, :tracksCount, :isSingleDiscAlbum)"); auto result = d->mInsertAlbumWithoutArtistQuery.prepare(insertAlbumWithoutArtistQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mInsertAlbumWithoutArtistQuery.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.lastError(); } } { auto initialUpdateTracksValidityQueryText = QStringLiteral("UPDATE `TracksMapping` SET `TrackValid` = 0"); auto result = d->mInitialUpdateTracksValidity.prepare(initialUpdateTracksValidityQueryText); if (!result) { 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.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.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.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.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.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.lastError(); } } { auto selectMusicSourceQueryText = QStringLiteral("SELECT `ID` FROM `DiscoverSource` WHERE `Name` = :name"); auto result = d->mSelectMusicSource.prepare(selectMusicSourceQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectMusicSource.lastError(); } } { auto selectTrackQueryText = QStringLiteral("SELECT " "tracks.`ID`, tracksMapping.`FileName` " "FROM `Tracks` tracks, `Artists` artist, `TracksMapping` tracksMapping " "WHERE " "tracks.`Title` = :title AND " "tracks.`AlbumID` = :album AND " "artist.`Name` = :artist AND " "artist.`ID` = tracks.`ArtistID` AND " "tracksMapping.`TrackID` = tracks.`ID` AND " "tracksMapping.`Priority` = 1"); auto result = d->mSelectTrackIdFromTitleAlbumIdArtistQuery.prepare(selectTrackQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackIdFromTitleAlbumIdArtistQuery.lastError(); } auto insertTrackQueryText = QStringLiteral("INSERT INTO `Tracks` (`ID`, `Title`, `AlbumID`, `ArtistID`, `TrackNumber`, `DiscNumber`, `Duration`, `Rating`) " "VALUES (:trackId, :title, :album, :artistId, :trackNumber, :discNumber, :trackDuration, :trackRating)"); result = d->mInsertTrackQuery.prepare(insertTrackQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mInsertTrackQuery.lastError(); } } { auto selectTrackQueryText = QStringLiteral("SELECT " "tracks.ID " "FROM `Tracks` tracks, `Albums` albums, `Artists` artist " "WHERE " "tracks.`Title` = :title AND " "tracks.`AlbumID` = albums.`ID` AND " "albums.`Title` = :album AND " "artist.`ID` = tracks.`ArtistID` AND " "artist.`Name` = :artist"); auto result = d->mSelectTrackIdFromTitleAlbumArtistQuery.prepare(selectTrackQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackIdFromTitleAlbumArtistQuery.lastError(); } } { auto selectAlbumTrackCountQueryText = QStringLiteral("SELECT `TracksCount` " "FROM `Albums`" "WHERE " "`ID` = :albumId"); auto result = d->mSelectAlbumTrackCountQuery.prepare(selectAlbumTrackCountQueryText); if (!result) { 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.lastError(); qDebug() << "DatabaseInterface::initRequest" << updateAlbumQueryText; } } { 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.lastError(); qDebug() << "DatabaseInterface::initRequest" << updateIsSingleDiscAlbumFromIdQueryText; } } { auto updateAlbumArtUriFromAlbumIdQueryText = QStringLiteral("UPDATE `Albums` " "SET `CoverFileName` = :coverFileName " "WHERE " "`ID` = :albumId"); auto result = d->mUpdateAlbumArtUriFromAlbumIdQuery.prepare(updateAlbumArtUriFromAlbumIdQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mUpdateAlbumArtUriFromAlbumIdQuery.lastError(); qDebug() << "DatabaseInterface::initRequest" << updateAlbumArtUriFromAlbumIdQueryText; } } { auto updateAlbumArtistFromAlbumIdQueryText = QStringLiteral("UPDATE `Albums` " "SET `ArtistID` = :artistId " "WHERE " "`ID` = :albumId"); auto result = d->mUpdateAlbumArtistFromAlbumIdQuery.prepare(updateAlbumArtistFromAlbumIdQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mUpdateAlbumArtistFromAlbumIdQuery.lastError(); qDebug() << "DatabaseInterface::initRequest" << updateAlbumArtistFromAlbumIdQueryText; } } { 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` " "FROM `Tracks` tracks, `Albums` album, `Artists` artist, `TracksMapping` tracksMapping " "LEFT JOIN `Artists` artistAlbum ON artistAlbum.`ID` = album.`ArtistID` " "WHERE " "artist.`Name` = :artistName AND " "tracks.`AlbumID` = album.`ID` AND " "artist.`ID` = tracks.`ArtistID` AND " "tracksMapping.`TrackID` = tracks.`ID` AND " "tracksMapping.`Priority` = 1 " "ORDER BY tracks.`Title` ASC, " "album.`Title` ASC"); auto result = d->mSelectTracksFromArtist.prepare(selectTracksFromArtistQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTracksFromArtist.lastError(); } } { auto selectArtistQueryText = QStringLiteral("SELECT `ID`, " "`Name` " "FROM `Artists` " "WHERE " "`ID` = :artistId"); auto result = d->mSelectArtistQuery.prepare(selectArtistQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << selectArtistQueryText << d->mSelectArtistQuery.lastError(); qDebug() << d->mTracksDatabase.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` " "FROM `Tracks` tracks, `Artists` artist, `Albums` album, `TracksMapping` tracksMapping " "LEFT JOIN `Artists` artistAlbum ON artistAlbum.`ID` = album.`ArtistID` " "WHERE " "tracks.`AlbumID` = album.`ID` AND " "artist.`ID` = tracks.`ArtistID` AND " "tracksMapping.`TrackID` = tracks.`ID` AND " "tracksMapping.`FileName` = :filePath AND " "tracksMapping.`Priority` = 1"); auto result = d->mSelectTrackFromFilePathQuery.prepare(selectTrackFromFilePathQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackFromFilePathQuery.lastError(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackFromFilePathQuery.lastQuery(); } } { auto removeTrackQueryText = QStringLiteral("DELETE FROM `Tracks` " "WHERE " "`ID` = :trackId"); auto result = d->mRemoveTrackQuery.prepare(removeTrackQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mRemoveTrackQuery.lastError(); qDebug() << "DatabaseInterface::initRequest" << d->mRemoveTrackQuery.lastQuery(); } } { auto removeAlbumQueryText = QStringLiteral("DELETE FROM `Albums` " "WHERE " "`ID` = :albumId"); auto result = d->mRemoveAlbumQuery.prepare(removeAlbumQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mRemoveAlbumQuery.lastError(); qDebug() << "DatabaseInterface::initRequest" << d->mRemoveAlbumQuery.lastQuery(); } } { auto removeAlbumQueryText = QStringLiteral("DELETE FROM `Artists` " "WHERE " "`ID` = :artistId"); auto result = d->mRemoveArtistQuery.prepare(removeAlbumQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mRemoveArtistQuery.lastError(); qDebug() << "DatabaseInterface::initRequest" << d->mRemoveArtistQuery.lastQuery(); } } transactionResult = finishTransaction(); d->mInitFinished = true; Q_EMIT requestsInitDone(); } qulonglong DatabaseInterface::insertAlbum(const QString &title, const QString &albumArtist, const QUrl &albumArtURI, int tracksCount, bool isSingleDiscAlbum) { auto result = qulonglong(0); if (title.isEmpty()) { return result; } d->mSelectAlbumIdFromTitleQuery.bindValue(QStringLiteral(":title"), title); d->mSelectAlbumIdFromTitleQuery.bindValue(QStringLiteral(":artistId"), insertArtist(albumArtist)); auto queryResult = d->mSelectAlbumIdFromTitleQuery.exec(); if (!queryResult || !d->mSelectAlbumIdFromTitleQuery.isSelect() || !d->mSelectAlbumIdFromTitleQuery.isActive()) { qDebug() << "DatabaseInterface::insertAlbum" << d->mSelectAlbumIdFromTitleQuery.lastQuery(); qDebug() << "DatabaseInterface::insertAlbum" << d->mSelectAlbumIdFromTitleQuery.boundValues(); qDebug() << "DatabaseInterface::insertAlbum" << d->mSelectAlbumIdFromTitleQuery.lastError(); d->mSelectAlbumIdFromTitleQuery.finish(); return result; } if (d->mSelectAlbumIdFromTitleQuery.next()) { result = d->mSelectAlbumIdFromTitleQuery.record().value(0).toULongLong(); d->mSelectAlbumIdFromTitleQuery.finish(); return result; } d->mSelectAlbumIdFromTitleQuery.finish(); QSqlQuery insertAlbumQuery; if (!albumArtist.isEmpty()) { insertAlbumQuery = d->mInsertAlbumQuery; } else { insertAlbumQuery = d->mInsertAlbumWithoutArtistQuery; } insertAlbumQuery.bindValue(QStringLiteral(":albumId"), d->mAlbumId); insertAlbumQuery.bindValue(QStringLiteral(":title"), title); if (!albumArtist.isEmpty()) { insertAlbumQuery.bindValue(QStringLiteral(":artistId"), insertArtist(albumArtist)); } insertAlbumQuery.bindValue(QStringLiteral(":coverFileName"), albumArtURI); insertAlbumQuery.bindValue(QStringLiteral(":tracksCount"), tracksCount); insertAlbumQuery.bindValue(QStringLiteral(":isSingleDiscAlbum"), isSingleDiscAlbum); queryResult = insertAlbumQuery.exec(); if (!queryResult || !insertAlbumQuery.isActive()) { qDebug() << "DatabaseInterface::insertAlbum" << insertAlbumQuery.lastQuery(); qDebug() << "DatabaseInterface::insertAlbum" << insertAlbumQuery.boundValues(); qDebug() << "DatabaseInterface::insertAlbum" << insertAlbumQuery.lastError(); insertAlbumQuery.finish(); return result; } result = d->mAlbumId; ++d->mAlbumId; insertAlbumQuery.finish(); 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()) { 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); if (!album.albumArtURI().isValid() || album.albumArtURI() == albumArtUri) { d->mUpdateAlbumArtUriFromAlbumIdQuery.bindValue(QStringLiteral(":albumId"), albumId); d->mUpdateAlbumArtUriFromAlbumIdQuery.bindValue(QStringLiteral(":coverFileName"), albumArtUri); result = d->mUpdateAlbumArtUriFromAlbumIdQuery.exec(); if (!result || !d->mUpdateAlbumArtUriFromAlbumIdQuery.isActive()) { 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)) { d->mUpdateAlbumArtistFromAlbumIdQuery.bindValue(QStringLiteral(":albumId"), albumId); d->mUpdateAlbumArtistFromAlbumIdQuery.bindValue(QStringLiteral(":artistId"), insertArtist(currentTrack.albumArtist())); result = d->mUpdateAlbumArtistFromAlbumIdQuery.exec(); if (!result || !d->mUpdateAlbumArtistFromAlbumIdQuery.isActive()) { qDebug() << "DatabaseInterface::updateIsSingleDiscAlbumFromId" << d->mUpdateAlbumArtistFromAlbumIdQuery.lastQuery(); qDebug() << "DatabaseInterface::updateIsSingleDiscAlbumFromId" << d->mUpdateAlbumArtistFromAlbumIdQuery.boundValues(); qDebug() << "DatabaseInterface::updateIsSingleDiscAlbumFromId" << d->mUpdateAlbumArtistFromAlbumIdQuery.lastError(); d->mUpdateAlbumArtistFromAlbumIdQuery.finish(); return modifiedAlbum; } d->mUpdateAlbumArtistFromAlbumIdQuery.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()) { 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()) { 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()) { 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()) { 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()) { 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()) { 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) { qulonglong resultId = 0; if (oneTrack.albumArtist().isEmpty()) { return resultId; } auto albumId = insertAlbum(oneTrack.albumName(), (oneTrack.isValidAlbumArtist() ? oneTrack.albumArtist() : QString()), covers[oneTrack.albumName()], 0, true); if (albumId == 0) { return resultId; } auto otherTrackId = internalTrackIdFromTitleAlbumArtist(oneTrack.title(), oneTrack.albumName(), oneTrack.artist()); bool isModifiedTrack = otherTrackId != 0; if (isModifiedTrack) { originTrackId = otherTrackId; removeTrackInDatabase(originTrackId); } else { originTrackId = d->mTrackId; } resultId = originTrackId; d->mInsertTrackQuery.bindValue(QStringLiteral(":trackId"), originTrackId); d->mInsertTrackQuery.bindValue(QStringLiteral(":title"), oneTrack.title()); d->mInsertTrackQuery.bindValue(QStringLiteral(":album"), albumId); d->mInsertTrackQuery.bindValue(QStringLiteral(":artistId"), insertArtist(oneTrack.artist())); 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()); auto result = d->mInsertTrackQuery.exec(); if (result && d->mInsertTrackQuery.isActive()) { d->mInsertTrackQuery.finish(); if (!isModifiedTrack) { ++d->mTrackId; } updateTrackOrigin(originTrackId, oneTrack.resourceURI()); if (isModifiedTrack) { Q_EMIT trackModified(internalTrackFromDatabaseId(originTrackId)); modifiedAlbumIds.insert(albumId); } else { Q_EMIT trackAdded(originTrackId); } if (updateAlbumFromId(albumId, covers[oneTrack.albumName()], oneTrack)) { modifiedAlbumIds.insert(albumId); } if (updateTracksCount(albumId)) { modifiedAlbumIds.insert(albumId); } } else { d->mInsertTrackQuery.finish(); qDebug() << "DatabaseInterface::modifyTracksList" << d->mInsertTrackQuery.lastQuery(); qDebug() << "DatabaseInterface::modifyTracksList" << d->mInsertTrackQuery.boundValues(); qDebug() << "DatabaseInterface::modifyTracksList" << 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.setValid(true); return result; } +void DatabaseInterface::internalRemoveTracksList(const QList &removedTracks) +{ + QList willRemoveTrack; + + qDebug() << "DatabaseInterface::internalRemoveTracksList" << removedTracks; + + for (const auto &removedTrackFileName : removedTracks) { + d->mSelectTrackFromFilePathQuery.bindValue(QStringLiteral(":filePath"), removedTrackFileName.toString()); + + auto result = d->mSelectTrackFromFilePathQuery.exec(); + + if (!result || !d->mSelectTrackFromFilePathQuery.isSelect() || !d->mSelectTrackFromFilePathQuery.isActive()) { + qDebug() << "DatabaseInterface::removeTracksList" << d->mSelectTrackFromFilePathQuery.lastQuery(); + qDebug() << "DatabaseInterface::removeTracksList" << d->mSelectTrackFromFilePathQuery.boundValues(); + qDebug() << "DatabaseInterface::removeTracksList" << d->mSelectTrackFromFilePathQuery.lastError(); + + continue; + } + + while (d->mSelectTrackFromFilePathQuery.next()) { + const auto ¤tRecord = d->mSelectTrackFromFilePathQuery.record(); + + willRemoveTrack.push_back(buildTrackFromDatabaseRecord(currentRecord)); + } + + d->mSelectTrackFromFilePathQuery.finish(); + } + + QSet modifiedAlbums; + + for (const auto &oneRemovedTrack : willRemoveTrack) { + removeTrackInDatabase(oneRemovedTrack.databaseId()); + Q_EMIT trackRemoved(oneRemovedTrack.databaseId()); + + const auto &modifiedAlbumId = internalAlbumIdFromTitle(oneRemovedTrack.albumName()); + const auto &allArtistTracks = internalTracksFromAuthor(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 (allArtistTracks.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); + } + } +} + 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()) { 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->mRemoveTrackQuery.bindValue(QStringLiteral(":trackId"), trackId); auto result = d->mRemoveTrackQuery.exec(); if (!result || !d->mRemoveTrackQuery.isActive()) { 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->mRemoveAlbumQuery.bindValue(QStringLiteral(":albumId"), albumId); auto result = d->mRemoveAlbumQuery.exec(); if (!result || !d->mRemoveAlbumQuery.isActive()) { 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()) { 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()) { 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()) { 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) const { auto allTracks = QList(); d->mSelectTrackQuery.bindValue(QStringLiteral(":albumId"), albumId); auto result = d->mSelectTrackQuery.exec(); if (!result || !d->mSelectTrackQuery.isSelect() || !d->mSelectTrackQuery.isActive()) { 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) const { bool isModified = false; d->mSelectAlbumTrackCountQuery.bindValue(QStringLiteral(":albumId"), albumId); auto result = d->mSelectAlbumTrackCountQuery.exec(); if (!result || !d->mSelectAlbumTrackCountQuery.isSelect() || !d->mSelectAlbumTrackCountQuery.isActive()) { 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()) { 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()) { 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) const { auto retrievedAlbum = MusicAlbum(); d->mSelectAlbumQuery.bindValue(QStringLiteral(":albumId"), albumId); auto result = d->mSelectAlbumQuery.exec(); if (!result || !d->mSelectAlbumQuery.isSelect() || !d->mSelectAlbumQuery.isActive()) { 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::internalAlbumFromTitle(const QString &title) { auto result = MusicAlbum(); d->mSelectAlbumIdFromTitleQuery.bindValue(QStringLiteral(":title"), title); auto queryResult = d->mSelectAlbumIdFromTitleQuery.exec(); if (!queryResult || !d->mSelectAlbumIdFromTitleQuery.isSelect() || !d->mSelectAlbumIdFromTitleQuery.isActive()) { qDebug() << "DatabaseInterface::albumFromTitleAndAuthor" << d->mSelectAlbumIdFromTitleQuery.lastQuery(); qDebug() << "DatabaseInterface::albumFromTitleAndAuthor" << d->mSelectAlbumIdFromTitleQuery.boundValues(); qDebug() << "DatabaseInterface::albumFromTitleAndAuthor" << d->mSelectAlbumIdFromTitleQuery.lastError(); d->mSelectAlbumIdFromTitleQuery.finish(); return result; } if (!d->mSelectAlbumIdFromTitleQuery.next()) { d->mSelectAlbumIdFromTitleQuery.finish(); return result; } auto albumId = d->mSelectAlbumIdFromTitleQuery.record().value(0).toULongLong(); d->mSelectAlbumIdFromTitleQuery.finish(); result = internalAlbumFromId(albumId); return result; } qulonglong DatabaseInterface::internalAlbumIdFromTitle(const QString &title) { auto result = qulonglong(0); d->mSelectAlbumIdFromTitleQuery.bindValue(QStringLiteral(":title"), title); auto queryResult = d->mSelectAlbumIdFromTitleQuery.exec(); if (!queryResult || !d->mSelectAlbumIdFromTitleQuery.isSelect() || !d->mSelectAlbumIdFromTitleQuery.isActive()) { qDebug() << "DatabaseInterface::albumFromTitleAndAuthor" << d->mSelectAlbumIdFromTitleQuery.lastQuery(); qDebug() << "DatabaseInterface::albumFromTitleAndAuthor" << d->mSelectAlbumIdFromTitleQuery.boundValues(); qDebug() << "DatabaseInterface::albumFromTitleAndAuthor" << d->mSelectAlbumIdFromTitleQuery.lastError(); d->mSelectAlbumIdFromTitleQuery.finish(); return result; } if (!d->mSelectAlbumIdFromTitleQuery.next()) { d->mSelectAlbumIdFromTitleQuery.finish(); return result; } result = d->mSelectAlbumIdFromTitleQuery.record().value(0).toULongLong(); d->mSelectAlbumIdFromTitleQuery.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()) { 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::internalTrackIdFromTitleAlbumArtist(const QString &title, const QString &album, const QString &artist) const { auto result = qulonglong(0); if (!d) { return result; } d->mSelectTrackIdFromTitleAlbumArtistQuery.bindValue(QStringLiteral(":title"), title); d->mSelectTrackIdFromTitleAlbumArtistQuery.bindValue(QStringLiteral(":album"), album); d->mSelectTrackIdFromTitleAlbumArtistQuery.bindValue(QStringLiteral(":artist"), artist); auto queryResult = d->mSelectTrackIdFromTitleAlbumArtistQuery.exec(); if (!queryResult || !d->mSelectTrackIdFromTitleAlbumArtistQuery.isSelect() || !d->mSelectTrackIdFromTitleAlbumArtistQuery.isActive()) { qDebug() << "DatabaseInterface::trackIdFromTitleAlbumArtist" << d->mSelectTrackIdFromTitleAlbumArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::trackIdFromTitleAlbumArtist" << d->mSelectTrackIdFromTitleAlbumArtistQuery.boundValues(); qDebug() << "DatabaseInterface::trackIdFromTitleAlbumArtist" << d->mSelectTrackIdFromTitleAlbumArtistQuery.lastError(); d->mSelectTrackIdFromTitleAlbumArtistQuery.finish(); return result; } if (d->mSelectTrackIdFromTitleAlbumArtistQuery.next()) { result = d->mSelectTrackIdFromTitleAlbumArtistQuery.record().value(0).toInt(); } d->mSelectTrackIdFromTitleAlbumArtistQuery.finish(); return result; } qulonglong DatabaseInterface::internalTrackIdFromFileName(const QUrl &fileName) const { 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()) { 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; } QVariant DatabaseInterface::internalAlbumDataFromId(qulonglong albumId, DatabaseInterface::AlbumData dataType) { auto result = QVariant(); auto currentAlbum = internalAlbumFromId(albumId); if (!currentAlbum.isValid()) { return result; } switch(dataType) { case DatabaseInterface::AlbumData::Id: result = currentAlbum.id(); break; case DatabaseInterface::AlbumData::Image: result = currentAlbum.albumArtURI(); break; case DatabaseInterface::AlbumData::Title: result = currentAlbum.title(); break; case DatabaseInterface::AlbumData::Artist: result = currentAlbum.artist(); break; case DatabaseInterface::AlbumData::TracksCount: result = currentAlbum.tracksCount(); break; } return result; } QList DatabaseInterface::internalTracksFromAuthor(const QString &artistName) const { auto allTracks = QList(); d->mSelectTracksFromArtist.bindValue(QStringLiteral(":artistName"), artistName); auto result = d->mSelectTracksFromArtist.exec(); if (!result || !d->mSelectTracksFromArtist.isSelect() || !d->mSelectTracksFromArtist.isActive()) { 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; } #include "moc_databaseinterface.cpp" diff --git a/src/databaseinterface.h b/src/databaseinterface.h index cbc79479..0498a839 100644 --- a/src/databaseinterface.h +++ b/src/databaseinterface.h @@ -1,176 +1,180 @@ /* * 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 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 = 0); virtual ~DatabaseInterface(); Q_INVOKABLE void init(const QString &dbName, const QString &databaseFileName = {}); MusicAlbum albumFromTitle(const QString &title); QList allTracks() const; QList allTracksFromSource(const QString &musicSource) const; QList allInvalidTracksFromSource(const QString &musicSource) const; QList allAlbums(); QList allArtists() const; QList tracksFromAuthor(const QString &artistName) const; MusicAudioTrack trackFromDatabaseId(qulonglong id); qulonglong trackIdFromTitleAlbumArtist(const QString &title, const QString &album, const QString &artist) const; void applicationAboutToQuit(); + void removeAllTracksFromSource(const QString &sourceName); + 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(); 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); private: bool startTransaction() const; bool finishTransaction() const; bool rollBackTransaction() const; QList fetchTracks(qulonglong albumId) const; bool updateTracksCount(qulonglong albumId) const; MusicArtist internalArtistFromId(qulonglong artistId) const; MusicAlbum internalAlbumFromId(qulonglong albumId) const; MusicAlbum internalAlbumFromTitle(const QString &title); qulonglong internalAlbumIdFromTitle(const QString &title); MusicAudioTrack internalTrackFromDatabaseId(qulonglong id); qulonglong internalTrackIdFromTitleAlbumArtist(const QString &title, const QString &album, const QString &artist) const; qulonglong internalTrackIdFromFileName(const QUrl &fileName) const; QVariant internalAlbumDataFromId(qulonglong albumId, AlbumData dataType); QList internalTracksFromAuthor(const QString &artistName) const; void initDatabase() const; void initRequest(); qulonglong insertAlbum(const QString &title, const QString &albumArtist, 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); MusicAudioTrack buildTrackFromDatabaseRecord(const QSqlRecord &trackRecord) const; + void internalRemoveTracksList(const QList &removedTracks); + DatabaseInterfacePrivate *d; }; #endif // DATABASEINTERFACE_H diff --git a/src/elisaapplication.cpp b/src/elisaapplication.cpp index 1725c5e8..9f06881a 100644 --- a/src/elisaapplication.cpp +++ b/src/elisaapplication.cpp @@ -1,144 +1,156 @@ /* * Copyright 2017 Matthieu Gallien * Copyright (C) 2012 Aleix Pol Gonzalez * * 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 "elisaapplication.h" +#include "elisa_settings.h" + #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND #include #include #include #include #endif -#if defined KF5ConfigWidgets_FOUND && KF5ConfigWidgets_FOUND #include -#endif - -#if defined KF5Config_FOUND && KF5Config_FOUND #include -#endif #if defined KF5CoreAddons_FOUND && KF5CoreAddons_FOUND #include #endif #include #include #include #include #include #include #include #include ElisaApplication::ElisaApplication(QObject *parent) : QObject(parent) #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND , mCollection(this) #endif { setupActions(); + + mConfigurationDialog.addModule(QStringLiteral("kcm_elisa_local_file")); } void ElisaApplication::setupActions() { #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND auto quitAction = KStandardAction::quit(QCoreApplication::instance(), &QCoreApplication::quit, &mCollection); mCollection.addAction(QStringLiteral("file_quit"), quitAction); if (KAuthorized::authorizeAction(QStringLiteral("help_contents"))) { auto mHandBookAction = KStandardAction::helpContents(this, &ElisaApplication::appHelpActivated, &mCollection); mCollection.addAction(mHandBookAction->objectName(), mHandBookAction); } if (KAuthorized::authorizeAction(QStringLiteral("help_report_bug")) && !KAboutData::applicationData().bugAddress().isEmpty()) { auto mReportBugAction = KStandardAction::reportBug(this, &ElisaApplication::reportBug, &mCollection); mCollection.addAction(mReportBugAction->objectName(), mReportBugAction); } if (KAuthorized::authorizeAction(QStringLiteral("help_about_app"))) { auto mAboutAppAction = KStandardAction::aboutApp(this, &ElisaApplication::aboutApplication, this); mCollection.addAction(mAboutAppAction->objectName(), mAboutAppAction); } - auto mKeyBindignsAction = KStandardAction::keyBindings(this, &ElisaApplication::configureShortcuts, this); - mCollection.addAction(mKeyBindignsAction->objectName(), mKeyBindignsAction); + if (KAuthorized::authorizeAction(QStringLiteral("options_configure"))) { + auto mPreferencesAction = KStandardAction::preferences(this, &ElisaApplication::configureElisa, this); + mCollection.addAction(mPreferencesAction->objectName(), mPreferencesAction); + } + + if (KAuthorized::authorizeAction(QStringLiteral("options_configure_keybinding"))) { + auto mKeyBindignsAction = KStandardAction::keyBindings(this, &ElisaApplication::configureShortcuts, this); + mCollection.addAction(mKeyBindignsAction->objectName(), mKeyBindignsAction); + } mCollection.readSettings(); #endif } void ElisaApplication::appHelpActivated() { QDesktopServices::openUrl(QUrl(QStringLiteral("help:/"))); } void ElisaApplication::aboutApplication() { #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND static QPointer dialog; if (!dialog) { dialog = new KAboutApplicationDialog(KAboutData::applicationData(), nullptr); dialog->setAttribute(Qt::WA_DeleteOnClose); } dialog->show(); #endif } void ElisaApplication::reportBug() { #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND static QPointer dialog; if (!dialog) { dialog = new KBugReport(KAboutData::applicationData(), nullptr); dialog->setAttribute(Qt::WA_DeleteOnClose); } dialog->show(); #endif } void ElisaApplication::configureShortcuts() { #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND KShortcutsDialog dlg(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, nullptr); dlg.setModal(true); dlg.addCollection(&mCollection); qDebug() << "saving shortcuts..." << dlg.configure(/*bSaveSettings*/); #endif } +void ElisaApplication::configureElisa() +{ + mConfigurationDialog.setModal(true); + mConfigurationDialog.show(); +} + QAction * ElisaApplication::action(const QString& name) { #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND return mCollection.action(name); #else Q_UNUSED(name); return new QAction(); #endif } QString ElisaApplication::iconName(const QIcon& icon) { return icon.name(); } #include "moc_elisaapplication.cpp" diff --git a/src/elisaapplication.h b/src/elisaapplication.h index 46c06b22..81610ba4 100644 --- a/src/elisaapplication.h +++ b/src/elisaapplication.h @@ -1,67 +1,73 @@ /* * 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. */ #ifndef ELISAAPPLICATION_H #define ELISAAPPLICATION_H #include "config-upnp-qt.h" #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND #include #endif +#include + #include #include class QIcon; class QAction; class ElisaApplication : public QObject { Q_OBJECT public: explicit ElisaApplication(QObject *parent = 0); void setupActions(); Q_INVOKABLE QAction* action(const QString& name); Q_INVOKABLE QString iconName(const QIcon& icon); Q_SIGNALS: public Q_SLOTS: void appHelpActivated(); void aboutApplication(); void reportBug(); void configureShortcuts(); + void configureElisa(); + private: #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND KActionCollection mCollection; #endif + KCMultiDialog mConfigurationDialog; + }; #endif // ELISAAPPLICATION_H diff --git a/src/file/filelistener.cpp b/src/file/filelistener.cpp index cabcbe2e..a8ba8e3e 100644 --- a/src/file/filelistener.cpp +++ b/src/file/filelistener.cpp @@ -1,43 +1,56 @@ /* * 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 "filelistener.h" #include "localfilelisting.h" #include "databaseinterface.h" #include class FileListenerPrivate { public: + LocalFileListing mLocalFileIndexer; + }; -FileListener::FileListener(QObject *parent) : AbstractFileListener(new LocalFileListing, parent), d(new FileListenerPrivate) +FileListener::FileListener(QObject *parent) : AbstractFileListener(parent), d(new FileListenerPrivate) { + setFileListing(&d->mLocalFileIndexer); } FileListener::~FileListener() { delete d; } +const LocalFileListing& FileListener::localFileIndexer() const +{ + return d->mLocalFileIndexer; +} + +void FileListener::setRootPath(const QString &rootPath) +{ + d->mLocalFileIndexer.setRootPath(rootPath); +} + #include "moc_filelistener.cpp" diff --git a/src/file/filelistener.h b/src/file/filelistener.h index c5e4fa7b..47a4a310 100644 --- a/src/file/filelistener.h +++ b/src/file/filelistener.h @@ -1,52 +1,55 @@ /* * 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 FILELISTENER_H #define FILELISTENER_H #include "../abstractfile/abstractfilelistener.h" #include #include #include class FileListenerPrivate; -class DatabaseInterface; -class MusicAudioTrack; +class LocalFileListing; class FileListener : public AbstractFileListener { Q_OBJECT public: explicit FileListener(QObject *parent = 0); virtual ~FileListener(); + const LocalFileListing& localFileIndexer() const; + Q_SIGNALS: public Q_SLOTS: + void setRootPath(const QString &rootPath); + private: FileListenerPrivate *d = nullptr; }; #endif // FILELISTENER_H diff --git a/src/file/localfilelisting.cpp b/src/file/localfilelisting.cpp index 92a7692b..7553717c 100644 --- a/src/file/localfilelisting.cpp +++ b/src/file/localfilelisting.cpp @@ -1,86 +1,87 @@ /* * 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(new LocalFileListingPrivate) { - const auto &musicLocations(QStandardPaths::standardLocations(QStandardPaths::MusicLocation)); - - if (musicLocations.isEmpty()) { - return; - } - - d->mRootPath = musicLocations.first(); } LocalFileListing::~LocalFileListing() { } 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() { scanDirectoryTree(d->mRootPath); + + Q_EMIT indexingFinished(); } #include "moc_localfilelisting.cpp" diff --git a/src/file/localfilelisting.h b/src/file/localfilelisting.h index 0fb59cbd..fe68ccd4 100644 --- a/src/file/localfilelisting.h +++ b/src/file/localfilelisting.h @@ -1,67 +1,69 @@ /* * 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 LOCALFILELISTING_H #define LOCALFILELISTING_H #include "../abstractfile/abstractfilelisting.h" #include #include class LocalFileListingPrivate; class LocalFileListing : public AbstractFileListing { Q_OBJECT Q_PROPERTY(QString rootPath READ rootPath WRITE setRootPath NOTIFY rootPathChanged) public: explicit LocalFileListing(QObject *parent = 0); virtual ~LocalFileListing(); QString rootPath() const; Q_SIGNALS: void rootPathChanged(); public Q_SLOTS: void setRootPath(const QString &rootPath); private: + void executeInit() override; + void triggerRefreshOfContent() override; std::unique_ptr d; }; #endif // LOCALFILELISTING_H diff --git a/src/localFileConfiguration/CMakeLists.txt b/src/localFileConfiguration/CMakeLists.txt new file mode 100644 index 00000000..468cc896 --- /dev/null +++ b/src/localFileConfiguration/CMakeLists.txt @@ -0,0 +1,31 @@ +set(KCM_ELISA_LOCAL_FILE_SRCS + localfileconfiguration.cpp + kcm_elisa_local_file.desktop + package/contents/ui/main.qml + package/metadata.desktop +) + +kconfig_add_kcfg_files(KCM_ELISA_LOCAL_FILE_SRCS ../elisa_settings.kcfgc ) +set(KCM_ELISA_LOCAL_FILE_SRCS + ${KCM_ELISA_LOCAL_FILE_SRCS} + ../elisa_core.kcfg +) + +add_library(kcm_elisa_local_file MODULE ${KCM_ELISA_LOCAL_FILE_SRCS}) + +target_link_libraries(kcm_elisa_local_file + KF5::ConfigCore + KF5::CoreAddons + KF5::Declarative + KF5::I18n + KF5::QuickAddons + KF5::ConfigWidgets +) + +kcoreaddons_desktop_to_json(kcm_elisa_local_file "kcm_elisa_local_file.desktop") + +install(FILES kcm_elisa_local_file.desktop DESTINATION ${SERVICES_INSTALL_DIR}) +install(TARGETS kcm_elisa_local_file DESTINATION ${PLUGIN_INSTALL_DIR}/kcms) + +kpackage_install_package(package kcm_elisa_local_file kcms) + diff --git a/src/localFileConfiguration/kcm_elisa_local_file.desktop b/src/localFileConfiguration/kcm_elisa_local_file.desktop new file mode 100644 index 00000000..fdb3b902 --- /dev/null +++ b/src/localFileConfiguration/kcm_elisa_local_file.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Type=Service +Icon=alarm +Exec=kcmshell5 kcm_elisa_local_file + +X-KDE-Library=kcm_elisa_local_file +X-KDE-ServiceTypes=KCModule +X-KDE-ParentComponents=elisa +X-KDE-PluginKeyword=kcm_elisa_local_file + +Name=Elisa Local Files Indexer +Comment=A configuration tool for Elisa music player local files indexer +X-KDE-Keywords=Elisa diff --git a/src/localFileConfiguration/localfileconfiguration.cpp b/src/localFileConfiguration/localfileconfiguration.cpp new file mode 100644 index 00000000..508e78a0 --- /dev/null +++ b/src/localFileConfiguration/localfileconfiguration.cpp @@ -0,0 +1,117 @@ +/* + * 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 "localfileconfiguration.h" + +#include "elisa_settings.h" + +#include +#include +#include + +#include + +K_PLUGIN_FACTORY_WITH_JSON(KCMElisaLocalFileFactory, + "kcm_elisa_local_file.json", + registerPlugin();) + +KCMElisaLocalFile::KCMElisaLocalFile(QObject* parent, const QVariantList &args) + : ConfigModule(parent, args) +{ + KAboutData *about = new KAboutData(QStringLiteral("kcm_elisa_local_file"), + i18n("Elisa Local Files Indexer Configuration"), + QStringLiteral("0.1"), {}, KAboutLicense::LGPL_V3, + i18n("Copyright 20017 Matthieu Gallien ")); + + about->addAuthor(i18n("Matthieu Gallien"),i18n("Author"), QStringLiteral("mgallien@mgallien.fr")); + setAboutData(about); + + auto configurationFileName = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); + configurationFileName += QStringLiteral("/elisarc"); + Elisa::ElisaConfiguration::instance(configurationFileName); + + connect(Elisa::ElisaConfiguration::self(), &Elisa::ElisaConfiguration::configChanged, + this, &KCMElisaLocalFile::configChanged); + + setRootPath(Elisa::ElisaConfiguration::rootPath()); + Elisa::ElisaConfiguration::setRootPath(mRootPath); + Elisa::ElisaConfiguration::self()->save(); +} + +KCMElisaLocalFile::~KCMElisaLocalFile() +{ +} + +QStringList KCMElisaLocalFile::rootPath() const +{ + return mRootPath; +} + +void KCMElisaLocalFile::defaults() +{ + setRootPath(QStandardPaths::standardLocations(QStandardPaths::MusicLocation)); +} + +void KCMElisaLocalFile::load() +{ + setRootPath(Elisa::ElisaConfiguration::rootPath()); +} + +void KCMElisaLocalFile::save() +{ + Elisa::ElisaConfiguration::setRootPath(mRootPath); + Elisa::ElisaConfiguration::self()->save(); +} + +void KCMElisaLocalFile::setRootPath(QStringList rootPath) +{ + if (mRootPath == rootPath) { + return; + } + + mRootPath.clear(); + for (const auto &onePath : rootPath) { + if (onePath.startsWith(QStringLiteral("file:///"))) { + mRootPath.push_back(onePath.mid(7)); + } else if (onePath.startsWith(QStringLiteral("file:/"))) { + mRootPath.push_back(onePath.mid(5)); + } else { + mRootPath.push_back(onePath); + } + } + + if (mRootPath.isEmpty()) { + for (const auto &musicPath : QStandardPaths::standardLocations(QStandardPaths::MusicLocation)) { + mRootPath.push_back(musicPath); + } + } + + Q_EMIT rootPathChanged(mRootPath); + + setNeedsSave(true); + Q_EMIT needsSaveChanged(); +} + +void KCMElisaLocalFile::configChanged() +{ + setRootPath(Elisa::ElisaConfiguration::rootPath()); +} + + +#include "localfileconfiguration.moc" diff --git a/src/file/localfilelisting.h b/src/localFileConfiguration/localfileconfiguration.h similarity index 57% copy from src/file/localfilelisting.h copy to src/localFileConfiguration/localfileconfiguration.h index 0fb59cbd..878d26d6 100644 --- a/src/file/localfilelisting.h +++ b/src/localFileConfiguration/localfileconfiguration.h @@ -1,67 +1,68 @@ /* - * Copyright 2016-2017 Matthieu Gallien + * 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. */ -#ifndef LOCALFILELISTING_H -#define LOCALFILELISTING_H +#if !defined LOCALFILECONFIGURATION_H_ +#define LOCALFILECONFIGURATION_H_ -#include "../abstractfile/abstractfilelisting.h" +#include +#include -#include - -#include - -class LocalFileListingPrivate; - -class LocalFileListing : public AbstractFileListing +class KCMElisaLocalFile : public KQuickAddons::ConfigModule { Q_OBJECT - Q_PROPERTY(QString rootPath + Q_PROPERTY(QStringList rootPath READ rootPath WRITE setRootPath NOTIFY rootPathChanged) public: - explicit LocalFileListing(QObject *parent = 0); + explicit KCMElisaLocalFile(QObject *parent, const QVariantList &args); - virtual ~LocalFileListing(); + virtual ~KCMElisaLocalFile(); - QString rootPath() const; + QStringList rootPath() const; Q_SIGNALS: - void rootPathChanged(); + void rootPathChanged(QStringList rootPath); public Q_SLOTS: - void setRootPath(const QString &rootPath); + void defaults() override final; -private: + void load() override final; - void triggerRefreshOfContent() override; + void save() override final; - std::unique_ptr d; + void setRootPath(QStringList rootPath); -}; +private Q_SLOTS: + void configChanged(); +private: + + QStringList mRootPath; + +}; -#endif // LOCALFILELISTING_H +#endif diff --git a/src/localFileConfiguration/package/contents/ui/main.qml b/src/localFileConfiguration/package/contents/ui/main.qml new file mode 100644 index 00000000..5d09733f --- /dev/null +++ b/src/localFileConfiguration/package/contents/ui/main.qml @@ -0,0 +1,154 @@ +/* + * 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. + */ + +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.2 +import QtQml.Models 2.3 +import org.kde.kcm 1.0 +import org.kde.plasma.core 2.0 as PlasmaCore + +Item { + //implicitWidth and implicitHeight will be used as initial size + //when loaded in kcmshell5 + implicitWidth: units.gridUnit * 20 + implicitHeight: units.gridUnit * 20 + + ConfigModule.buttons: ConfigModule.Help|ConfigModule.Apply + + SystemPalette { + id: myPalette + colorGroup: SystemPalette.Active + } + + Component { + id: highlightBar + + Rectangle { + width: 200; height: 50 + color: myPalette.highlight + } + } + + Component { + id: pathDelegate + + Item { + id: delegateItem + + height: 3 * units.gridUnit + + width: pathList.width + + Rectangle { + anchors.fill: parent + anchors.margins: 0.1 * units.gridUnit + + color: myPalette.base + + MouseArea { + anchors.fill: parent + + hoverEnabled: true + + onEntered: pathList.currentIndex = delegateItem.DelegateModel.itemsIndex + + Label { + text: modelData + + anchors.centerIn: parent + } + + ToolButton { + iconName: 'list-remove' + + anchors.top: parent.top + anchors.right: parent.right + + onClicked: + { + var oldPaths = kcm.rootPath + oldPaths.splice(delegateItem.DelegateModel.itemsIndex, 1) + kcm.rootPath = oldPaths + } + } + } + } + } + } + + RowLayout { + spacing: 0 + + anchors.fill: parent + + ScrollView { + flickableItem.boundsBehavior: Flickable.StopAtBounds + + Layout.fillWidth: true + Layout.fillHeight: true + + ListView { + id:pathList + + anchors.fill: parent + + model: DelegateModel { + model: kcm.rootPath + + delegate: pathDelegate + } + + highlight: highlightBar + } + } + + ColumnLayout { + Layout.fillHeight: true + Layout.leftMargin: 0.3 * units.gridUnit + + Button { + text: 'Add new path' + onClicked: fileDialog.open() + + Layout.alignment: Qt.AlignTop | Qt.AlignLeft + + FileDialog { + id: fileDialog + title: "Please choose a file" + folder: shortcuts.home + selectFolder: true + + visible: false + + onAccepted: { + var oldPaths = kcm.rootPath + oldPaths.push(fileDialog.fileUrls) + kcm.rootPath = oldPaths + } + } + } + + Item { + Layout.fillHeight: true + } + } + } +} diff --git a/src/localFileConfiguration/package/metadata.desktop b/src/localFileConfiguration/package/metadata.desktop new file mode 100644 index 00000000..7ffb54bc --- /dev/null +++ b/src/localFileConfiguration/package/metadata.desktop @@ -0,0 +1,16 @@ +[Desktop Entry] +Name=Elisa +Comment=Configure Local Files Indexer of Elisa Music Player +Icon=elisa +Type=Service +X-KDE-ParentApp= +X-KDE-PluginInfo-Author=Matthieu Gallien +X-KDE-PluginInfo-Email=matthieu_gallien@yahoo.fr +X-KDE-PluginInfo-License=LGPLv3 +X-KDE-PluginInfo-Name=kcm_elisa_local_file +X-KDE-PluginInfo-Version= +X-KDE-PluginInfo-Website= +X-KDE-ServiceTypes=Plasma/Generic + +X-Plasma-MainScript=ui/main.qml +X-Plasma-RemoteLocation= diff --git a/src/musiclistenersmanager.cpp b/src/musiclistenersmanager.cpp index 1f827899..69483eaa 100644 --- a/src/musiclistenersmanager.cpp +++ b/src/musiclistenersmanager.cpp @@ -1,180 +1,260 @@ /* * 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 "elisa_settings.h" #include #include #include #include #include +#include +#include +#include +#include class MusicListenersManagerPrivate { public: QThread mDatabaseThread; #if defined UPNPQT_FOUND && UPNPQT_FOUND UpnpListener mUpnpListener; #endif #if defined KF5Baloo_FOUND && KF5Baloo_FOUND - BalooListener mBalooListener; + QScopedPointer mBalooListener; #endif -#if (!defined KF5Baloo_FOUND || !KF5Baloo_FOUND) && defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND - FileListener mFileListener; +#if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND + QList> mFileListener; #endif DatabaseInterface mDatabaseInterface; + QFileSystemWatcher mConfigFileWatcher; + }; MusicListenersManager::MusicListenersManager(QObject *parent) : QObject(parent), d(new MusicListenersManagerPrivate) { 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()); } MusicListenersManager::~MusicListenersManager() { delete d; } 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(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::newArtistInList, helper, &TracksListener::newArtistInList); } void MusicListenersManager::databaseReady() { -#if defined KF5Baloo_FOUND && KF5Baloo_FOUND - d->mBalooListener.setDatabaseInterface(&d->mDatabaseInterface); - d->mBalooListener.moveToThread(&d->mDatabaseThread); - connect(this, &MusicListenersManager::applicationIsTerminating, - &d->mBalooListener, &BalooListener::applicationAboutToQuit, Qt::BlockingQueuedConnection); - connect(this, &MusicListenersManager::databaseIsReady, - &d->mBalooListener, &BalooListener::databaseReady); -#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::BlockingQueuedConnection); - connect(this, &MusicListenersManager::databaseIsReady, - &d->mUpnpListener, &UpnpListener::databaseReady); -#endif - -#if (!defined KF5Baloo_FOUND || !KF5Baloo_FOUND) && defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND - d->mFileListener.setDatabaseInterface(&d->mDatabaseInterface); - d->mFileListener.moveToThread(&d->mDatabaseThread); - connect(this, &MusicListenersManager::applicationIsTerminating, - &d->mFileListener, &FileListener::applicationAboutToQuit, Qt::BlockingQueuedConnection); - connect(this, &MusicListenersManager::databaseIsReady, - &d->mFileListener, &FileListener::databaseReady); -#endif + configChanged(); Q_EMIT databaseIsReady(); } void MusicListenersManager::applicationAboutToQuit() { d->mDatabaseInterface.applicationAboutToQuit(); Q_EMIT applicationIsTerminating(); d->mDatabaseThread.exit(); d->mDatabaseThread.wait(); } +void MusicListenersManager::showConfiguration() +{ +} + +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->setDatabaseInterface(&d->mDatabaseInterface); + d->mBalooListener->moveToThread(&d->mDatabaseThread); + connect(this, &MusicListenersManager::applicationIsTerminating, + d->mBalooListener.data(), &BalooListener::applicationAboutToQuit, Qt::BlockingQueuedConnection); + connect(this, &MusicListenersManager::databaseIsReady, + d->mBalooListener.data(), &BalooListener::databaseReady); + connect(d->mBalooListener.data(), &BalooListener::indexingFinished, + this, &MusicListenersManager::indexingFinished); + } else if (!currentConfiguration->balooIndexer() && d->mBalooListener) { + 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::BlockingQueuedConnection); + connect(this, &MusicListenersManager::databaseIsReady, + &d->mUpnpListener, &UpnpListener::databaseReady); +#endif + +#if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND + 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](auto value)->bool {return value->localFileIndexer().rootPath() == oneRootPath;}); + if (itPath == d->mFileListener.end()) { + auto newFileIndexer = new FileListener; + + newFileIndexer->setDatabaseInterface(&d->mDatabaseInterface); + newFileIndexer->moveToThread(&d->mDatabaseThread); + connect(this, &MusicListenersManager::applicationIsTerminating, + newFileIndexer, &FileListener::applicationAboutToQuit, Qt::BlockingQueuedConnection); + connect(this, &MusicListenersManager::databaseIsReady, + newFileIndexer, &FileListener::databaseReady); + connect(newFileIndexer, &FileListener::indexingFinished, + this, &MusicListenersManager::indexingFinished); + + newFileIndexer->setRootPath(oneRootPath); + + d->mFileListener.push_back({newFileIndexer}); + + newFileIndexer->performInitialScan(); + } + } + } +#endif +} + #include "moc_musiclistenersmanager.cpp" diff --git a/src/musiclistenersmanager.h b/src/musiclistenersmanager.h index 668f7704..796ba453 100644 --- a/src/musiclistenersmanager.h +++ b/src/musiclistenersmanager.h @@ -1,92 +1,100 @@ /* * 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 #include "musicalbum.h" #include "musicartist.h" #include "musicaudiotrack.h" class MusicListenersManagerPrivate; class DatabaseInterface; class MediaPlayList; class MusicListenersManager : public QObject { Q_OBJECT Q_PROPERTY(DatabaseInterface* viewDatabase READ viewDatabase NOTIFY viewDatabaseChanged) public: explicit MusicListenersManager(QObject *parent = 0); virtual ~MusicListenersManager(); DatabaseInterface* viewDatabase() const; void subscribeForTracks(MediaPlayList *client); 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 databaseIsReady(); + void indexingFinished(); + public Q_SLOTS: void databaseReady(); void applicationAboutToQuit(); + void showConfiguration(); + +private Q_SLOTS: + + void configChanged(); + private: MusicListenersManagerPrivate *d; }; #endif // MUSICLISTENERSMANAGER_H diff --git a/src/upnpControl.cpp b/src/upnpControl.cpp index ec0c39fc..65c9e11f 100644 --- a/src/upnpControl.cpp +++ b/src/upnpControl.cpp @@ -1,233 +1,240 @@ /* * 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 "playlistcontroler.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 "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 #if defined KF5CoreAddons_FOUND && KF5CoreAddons_FOUND #include #endif #if defined KF5Crash_FOUND && KF5Crash_FOUND #include #endif #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.mgallien.QmlExtension", 1, 0, "UpnpSsdpEngine"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "UpnpDiscoverAllMusic"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "UpnpAbstractDevice"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "UpnpAbstractService"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "UpnpControlAbstractDevice"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "UpnpControlAbstractService"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "UpnpControlConnectionManager"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "UpnpControlMediaServer"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "UpnpContentDirectoryModel"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "DidlParser"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "UpnpControlContentDirectory"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "UpnpDeviceDescription"); qRegisterMetaType(); qRegisterMetaType >(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); #endif qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "MediaPlayList"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "PlayListControler"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "ManageMediaPlayerControl"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "ManageHeaderBar"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "ManageAudioPlayer"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "MusicStatistics"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "ProgressIndicator"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "AllAlbumsModel"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "AllArtistsModel"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "AlbumModel"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "AllTracksModel"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "MusicListenersManager"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "SortFilterProxyModel"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "AlbumFilterProxyModel"); qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "AudioWrapper"); #if defined Qt5DBus_FOUND && Qt5DBus_FOUND qmlRegisterType("org.mgallien.QmlExtension", 1, 0, "Mpris2"); qRegisterMetaType(); #endif qRegisterMetaType(); qRegisterMetaType>("QHash"); qRegisterMetaType>("QList"); qRegisterMetaType>("QVector"); qRegisterMetaType>("QVector"); qRegisterMetaType>("QHash"); qRegisterMetaType("MusicAlbum"); qRegisterMetaType("MusicArtist"); qRegisterMetaType(); qmlRegisterUncreatableType("org.mgallien.QmlExtension", 1, 0, "ElisaApplication", QStringLiteral("only one and done in c++")); qRegisterMetaTypeStreamOperators("PlayListControler::PlayerState"); #if defined KF5CoreAddons_FOUND && KF5CoreAddons_FOUND KAboutData aboutData( QStringLiteral("elisa"), i18n("Elisa"), QStringLiteral("0.1"), i18n("A Simple Music Player written with KDE Frameworks"), KAboutLicense::LGPL_V3, i18n("(c) 2015-2017, Matthieu Gallien ")); aboutData.addAuthor(i18n("Matthieu Gallien"),i18n("Author"), QStringLiteral("mgallien@mgallien.fr")); KAboutData::setApplicationData(aboutData); #else QGuiApplication::setApplicationName(QStringLiteral("elisa")); QGuiApplication::setOrganizationDomain(QStringLiteral("kde.org")); QGuiApplication::setOrganizationName(QStringLiteral("KDE")); #endif #if defined KF5DBusAddons_FOUND && KF5DBusAddons_FOUND KDBusService elisaService(KDBusService::Unique); #endif KLocalizedString::setApplicationDomain("elisa"); ElisaApplication myApp; #if defined KF5CoreAddons_FOUND && KF5CoreAddons_FOUND QCommandLineParser parser; parser.addHelpOption(); parser.addVersionOption(); aboutData.setupCommandLine(&parser); parser.process(app); aboutData.processCommandLine(&parser); #endif #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(); + 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.load(QUrl(QStringLiteral("qrc:/MediaServer.qml"))); return app.exec(); }