diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e43e56c..17d58f0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,171 +1,186 @@ cmake_minimum_required(VERSION 3.5) project(elisa) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD 14) set(REQUIRED_QT_VERSION "5.10.0") find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Core Network Qml Quick Test Sql Multimedia Svg Gui Widgets QuickTest Concurrent QuickControls2) find_package(Qt5Core ${REQUIRED_QT_VERSION} CONFIG REQUIRED Private) set(REQUIRED_KF5_VERSION "5.48.0") find_package(ECM ${REQUIRED_KF5_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(ECMInstallIcons) include(FeatureSummary) include(ECMAddAppIcon) include(ECMAddTests) include(ECMQtDeclareLoggingCategory) if (NOT WIN32) find_package(Qt5DBus ${REQUIRED_QT_VERSION} CONFIG QUIET) set_package_properties(Qt5DBus PROPERTIES DESCRIPTION "Qt5 DBus is needed to provide MPris2 interface to allow remote control by the desktop workspace." TYPE OPTIONAL) endif() find_package(Qt5QuickWidgets ${REQUIRED_QT_VERSION} CONFIG QUIET) set_package_properties(Qt5QuickWidgets PROPERTIES DESCRIPTION "Qt5 Quick Widgets is needed at runtime to provide the interface." TYPE RUNTIME) find_package(Qt5QuickControls2 ${REQUIRED_QT_VERSION} CONFIG QUIET) set_package_properties(Qt5QuickControls2 PROPERTIES DESCRIPTION "Qt5 Quick Controls version 2 is needed at runtime to provide the interface." TYPE RUNTIME) if (ANDROID) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG QUIET OPTIONAL_COMPONENTS AndroidExtras) + set_package_properties(Qt5AndroidExtras PROPERTIES + DESCRIPTION "Qt5 AndroidExtras is needed to provide the Android integration." + TYPE REQUIRED) endif() +find_package(KF5Kirigami2 ${REQUIRED_KF5_VERSION} CONFIG QUIET) + +if (ANDROID) + set(KIRIGAMI_DEPENDENCY_TYPE REQUIRED) +else() + set(KIRIGAMI_DEPENDENCY_TYPE RECOMMENDED) +endif() + +set_package_properties(KF5Kirigami2 PROPERTIES + DESCRIPTION "KF5 Kirigami 2 is needed to provide the mobile UI components." + TYPE ${KIRIGAMI_DEPENDENCY_TYPE}) + find_package(KF5I18n ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5I18n PROPERTIES DESCRIPTION "KF5 text internationalization library." TYPE REQUIRED) find_package(KF5Declarative ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5Declarative PROPERTIES DESCRIPTION "Integration of QML and KDE work spaces." TYPE RECOMMENDED) find_package(KF5CoreAddons ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5CoreAddons PROPERTIES DESCRIPTION "Qt addon library with a collection of non-GUI utilities." TYPE REQUIRED) if (NOT WIN32) find_package(KF5Baloo ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5Baloo PROPERTIES DESCRIPTION "Baloo provides file searching and indexing." TYPE RECOMMENDED) endif() find_package(KF5FileMetaData ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5FileMetaData PROPERTIES DESCRIPTION "Provides a simple library for extracting metadata." TYPE RECOMMENDED) find_package(KF5DocTools ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5DocTools PROPERTIES DESCRIPTION "Create documentation from DocBook library." TYPE OPTIONAL) find_package(KF5XmlGui ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5XmlGui PROPERTIES DESCRIPTION "Framework for managing menu and toolbar actions." TYPE RECOMMENDED) find_package(KF5Config ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5Config PROPERTIES DESCRIPTION "Persistent platform-independent application settings." TYPE REQUIRED) find_package(KF5ConfigWidgets ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5ConfigWidgets PROPERTIES DESCRIPTION "Widgets for configuration dialogs." TYPE RECOMMENDED) find_package(KF5Crash ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5Crash PROPERTIES DESCRIPTION "Graceful handling of application crashes." TYPE OPTIONAL) if (NOT WIN32) find_package(KF5DBusAddons ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5DBusAddons PROPERTIES DESCRIPTION "Convenience classes for D-Bus." TYPE OPTIONAL) endif() find_package(KF5KCMUtils ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5KCMUtils PROPERTIES DESCRIPTION "KF5 Utilities for KDE System Settings modules library." TYPE RECOMMENDED) find_package(KF5Package ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5Package PROPERTIES DESCRIPTION "KF5 package management library needed to get the configuration dialogs." TYPE RECOMMENDED) find_package(KF5KIO ${REQUIRED_KF5_VERSION} CONFIG QUIET) set_package_properties(KF5KIO PROPERTIES DESCRIPTION "File management libraries used for file browsing." TYPE RECOMMENDED) find_package(UPNPQT CONFIG QUIET) set_package_properties(UPNPQT PROPERTIES DESCRIPTION "UPNP layer build with Qt 5. UPnP support is currently broken. You should probably avoid this dependency." URL "https://gitlab.com/homeautomationqt/upnp-player-qt" TYPE OPTIONAL) if (UPNPQT_FOUND) message(WARNING "UPnP support is experimental and may not work.") endif() find_package(LIBVLC QUIET) set_package_properties(LIBVLC PROPERTIES DESCRIPTION "libvlc allows to play music in Elisa" URL "https://www.videolan.org/vlc/libvlc.html" TYPE RECOMMENDED) 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") endif() configure_file(config-upnp-qt.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-upnp-qt.h ) add_subdirectory(src) add_subdirectory(icons) if (BUILD_TESTING) add_subdirectory(autotests) endif() 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} ) if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ki18n_install(po) endif() feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/data/AndroidManifest.xml b/data/AndroidManifest.xml index 33a35599..02bfddcc 100644 --- a/data/AndroidManifest.xml +++ b/data/AndroidManifest.xml @@ -1,82 +1,82 @@ - - + + - + - + diff --git a/data/src/org/kde/elisa/ElisaAndroidMusicScanner.java b/data/src/org/kde/elisa/ElisaAndroidMusicScanner.java new file mode 100644 index 00000000..5231112c --- /dev/null +++ b/data/src/org/kde/elisa/ElisaAndroidMusicScanner.java @@ -0,0 +1,87 @@ +package org.kde.elisa; + +import android.content.Context; +import android.database.Cursor; +import android.content.Intent; +import android.provider.MediaStore; + +/** + * Created by mgallien on 02/05/17. + */ + +public class ElisaAndroidMusicScanner { + private static String[] tracksRequestedColumns = { + MediaStore.Audio.Media._ID, + MediaStore.Audio.Media.TITLE, + MediaStore.Audio.Media.TRACK, + MediaStore.Audio.Media.YEAR, + MediaStore.Audio.Media.DURATION, + MediaStore.Audio.Media.DATA, + MediaStore.Audio.Media.ARTIST, + MediaStore.Audio.Media.ARTIST_ID, + MediaStore.Audio.Media.ALBUM, + MediaStore.Audio.Media.ALBUM_ID, + MediaStore.Audio.Media.COMPOSER, + }; + + private static String[] albumsRequestedColumns = { + MediaStore.Audio.Albums._ID, + MediaStore.Audio.Albums.ALBUM, + MediaStore.Audio.Albums.ALBUM_ART, + MediaStore.Audio.Albums.ARTIST, + MediaStore.Audio.Albums.NUMBER_OF_SONGS, + }; + + public static void listAudioFiles(Context ctx) + { + androidMusicScanTracksStarting(); + + //Some audio may be explicitly marked as not being music + String tracksSelection = MediaStore.Audio.Media.IS_MUSIC + " != 0"; + String tracksSortOrder = MediaStore.Audio.Media.DEFAULT_SORT_ORDER + " ASC"; + + Cursor tracksCursor = ctx.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, tracksRequestedColumns, tracksSelection, null, tracksSortOrder); + + tracksCursor.moveToFirst(); + + while(tracksCursor.moveToNext()) { + sendMusicFile(tracksCursor.getString(0) + "||" + tracksCursor.getString(1) + "||" + + tracksCursor.getString(2) + "||" + tracksCursor.getString(3) + "||" + + tracksCursor.getString(4) + "||" + tracksCursor.getString(5) + "||" + + tracksCursor.getString(6) + "||" + tracksCursor.getString(7) + "||" + + tracksCursor.getString(8) + "||" + tracksCursor.getString(9) + "||" + + tracksCursor.getString(10)); + } + + androidMusicScanTracksFinishing(); + + androidMusicScanAlbumsStarting(); + + //Some audio may be explicitly marked as not being music + String albumsSortOrder = MediaStore.Audio.Albums.DEFAULT_SORT_ORDER + " ASC"; + + Cursor albumsCursor = ctx.getContentResolver().query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, albumsRequestedColumns, null, null, albumsSortOrder); + + albumsCursor.moveToFirst(); + + while(albumsCursor.moveToNext()) { + sendMusicAlbum(albumsCursor.getString(0) + "||" + albumsCursor.getString(1) + "||" + + albumsCursor.getString(2) + "||" + albumsCursor.getString(3) + "||" + albumsCursor.getString(4)); + } + + androidMusicScanAlbumsFinishing(); + } + + private static native void androidMusicScanTracksStarting(); + + private static native void sendMusicFile(String musicFile); + + private static native void androidMusicScanTracksFinishing(); + + private static native void androidMusicScanAlbumsStarting(); + + private static native void sendMusicAlbum(String musicFile); + + private static native void androidMusicScanAlbumsFinishing(); + +} diff --git a/data/src/org/kde/elisa/ElisaService.java b/data/src/org/kde/elisa/ElisaService.java index 2807ddfb..df865007 100644 --- a/data/src/org/kde/elisa/ElisaService.java +++ b/data/src/org/kde/elisa/ElisaService.java @@ -1,16 +1,25 @@ package org.kde.elisa; import android.content.Context; +import android.database.Cursor; import android.content.Intent; +import android.provider.MediaStore; import org.qtproject.qt5.android.bindings.QtService; +class ElisaNatives { + + // declare the native method + public static native void sendMusicFile(String musicFile); +} + /** * Created by mgallien on 02/05/17. */ public class ElisaService extends QtService { public static void startMyService(Context ctx) { + //System.err.println("hello service"); ctx.startService(new Intent(ctx, ElisaService.class)); } } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 97c37a3b..d7c8da69 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,418 +1,445 @@ include_directories(${elisa_BINARY_DIR}) set(elisaLib_SOURCES mediaplaylist.cpp musicaudiotrack.cpp progressindicator.cpp databaseinterface.cpp musiclistenersmanager.cpp managemediaplayercontrol.cpp manageheaderbar.cpp manageaudioplayer.cpp trackslistener.cpp elisaapplication.cpp modeldataloader.cpp notificationitem.cpp topnotificationmanager.cpp elisautils.cpp datatype.cpp abstractfile/abstractfilelistener.cpp abstractfile/abstractfilelisting.cpp filescanner.cpp file/filelistener.cpp file/localfilelisting.cpp models/albummodel.cpp models/allalbumsmodel.cpp models/allartistsmodel.cpp models/alltracksmodel.cpp models/allgenresmodel.cpp models/abstractmediaproxymodel.cpp models/allalbumsproxymodel.cpp models/allartistsproxymodel.cpp models/alltracksproxymodel.cpp models/singlealbumproxymodel.cpp models/trackmetadatamodel.cpp ) if (LIBVLC_FOUND) set(elisaLib_SOURCES ${elisaLib_SOURCES} audiowrapper_libvlc.cpp - ) + ) else() set(elisaLib_SOURCES ${elisaLib_SOURCES} audiowrapper_qtmultimedia.cpp - ) + ) +endif() + +if (ANDROID) + set(elisaLib_SOURCES + ${elisaLib_SOURCES} + android/androidmusiclistener.cpp + ) endif() if (KF5KIO_FOUND) set(elisaLib_SOURCES ${elisaLib_SOURCES} models/filebrowsermodel.cpp models/filebrowserproxymodel.cpp - ) + ) endif() if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) set(elisaLib_SOURCES ${elisaLib_SOURCES} baloo/localbaloofilelisting.cpp baloo/baloolistener.cpp ) qt5_add_dbus_interface(elisaLib_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.main.xml baloo/main) qt5_add_dbus_interface(elisaLib_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.fileindexer.xml baloo/fileindexer) qt5_add_dbus_interface(elisaLib_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.scheduler.xml baloo/scheduler) qt5_add_dbus_adaptor(elisaLib_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.BalooWatcherApplication.xml baloo/localbaloofilelisting.h LocalBalooFileListing) endif() endif() if (Qt5DBus_FOUND) set(elisaLib_SOURCES ${elisaLib_SOURCES} mpris2/mpris2.cpp mpris2/mediaplayer2.cpp mpris2/mediaplayer2player.cpp - ) + ) endif() if (UPNPQT_FOUND) set(elisaLib_SOURCES ${elisaLib_SOURCES} upnp/upnpcontrolcontentdirectory.cpp upnp/upnpcontentdirectorymodel.cpp upnp/upnpcontrolconnectionmanager.cpp upnp/upnpcontrolmediaserver.cpp upnp/didlparser.cpp upnp/upnplistener.cpp upnp/upnpdiscoverallmusic.cpp ) endif() if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) qt5_add_dbus_interface(elisaLib_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.fileindexer.xml baloo/fileindexer) qt5_add_dbus_interface(elisaLib_SOURCES ${BALOO_DBUS_INTERFACES_DIR}/org.kde.baloo.scheduler.xml baloo/scheduler) set(elisaLib_SOURCES ${elisaLib_SOURCES} ../src/baloo/baloolistener.cpp ../src/baloo/localbaloofilelisting.cpp ) endif() endif() kconfig_add_kcfg_files(elisaLib_SOURCES ../src/elisa_settings.kcfgc ) set(elisaLib_SOURCES ${elisaLib_SOURCES} ../src/elisa_core.kcfg -) + ) add_library(elisaLib ${elisaLib_SOURCES}) target_link_libraries(elisaLib LINK_PUBLIC - Qt5::Multimedia + Qt5::Multimedia LINK_PRIVATE - Qt5::Core Qt5::Sql Qt5::Widgets Qt5::Concurrent Qt5::Qml - KF5::I18n KF5::CoreAddons - KF5::ConfigCore KF5::ConfigGui) + Qt5::Core Qt5::Sql Qt5::Widgets Qt5::Concurrent Qt5::Qml + KF5::I18n KF5::CoreAddons + KF5::ConfigCore KF5::ConfigGui) if (KF5FileMetaData_FOUND) target_link_libraries(elisaLib LINK_PRIVATE - KF5::FileMetaData - ) + KF5::FileMetaData + ) endif() if (KF5KIO_FOUND) target_link_libraries(elisaLib LINK_PUBLIC - KF5::KIOCore KF5::KIOFileWidgets KF5::KIOWidgets - ) + KF5::KIOCore KF5::KIOFileWidgets KF5::KIOWidgets + ) endif() if (KF5XmlGui_FOUND) target_link_libraries(elisaLib LINK_PUBLIC - KF5::XmlGui - ) + KF5::XmlGui + ) endif() if (KF5ConfigWidgets_FOUND) target_link_libraries(elisaLib LINK_PUBLIC - KF5::ConfigWidgets - ) + KF5::ConfigWidgets + ) endif() if (KF5KCMUtils_FOUND) target_link_libraries(elisaLib LINK_PUBLIC - KF5::KCMUtils - ) + KF5::KCMUtils + ) endif() if (KF5Baloo_FOUND) if (Qt5DBus_FOUND) target_link_libraries(elisaLib LINK_PUBLIC - KF5::Baloo - ) + KF5::Baloo + ) endif() endif() if (Qt5DBus_FOUND) target_link_libraries(elisaLib LINK_PUBLIC - Qt5::DBus - ) + Qt5::DBus + ) if (KF5DBusAddons_FOUND) target_link_libraries(elisaLib LINK_PUBLIC - KF5::DBusAddons - ) + KF5::DBusAddons + ) endif() endif() if (LIBVLC_FOUND) target_include_directories(elisaLib PRIVATE - ${LIBVLC_INCLUDE_DIR} - ) + ${LIBVLC_INCLUDE_DIR} + ) target_link_libraries(elisaLib LINK_PRIVATE - ${LIBVLC_LIBRARY} - ) + ${LIBVLC_LIBRARY} + ) +endif() + +if (ANDROID) + target_link_libraries(elisaLib + LINK_PUBLIC + Qt5::AndroidExtras + ) endif() generate_export_header(elisaLib BASE_NAME ElisaLib EXPORT_FILE_NAME elisaLib_export.h) set_target_properties(elisaLib PROPERTIES VERSION 0.1 SOVERSION 0 EXPORT_NAME ElisaLib -) + ) if (NOT APPLE AND NOT WIN32) install(TARGETS elisaLib LIBRARY DESTINATION ${KDE_INSTALL_FULL_LIBDIR}/elisa RUNTIME DESTINATION ${KDE_INSTALL_FULL_LIBDIR}/elisa BUNDLE DESTINATION ${KDE_INSTALL_FULL_LIBDIR}/elisa - ) + ) else() install(TARGETS elisaLib ${INSTALL_TARGETS_DEFAULT_ARGS}) endif() set(elisaqmlplugin_SOURCES elisaqmlplugin.cpp datatype.cpp elisautils.cpp embeddedcoverageimageprovider.cpp ) add_library(elisaqmlplugin SHARED ${elisaqmlplugin_SOURCES}) target_link_libraries(elisaqmlplugin LINK_PRIVATE - Qt5::Quick Qt5::Widgets - KF5::ConfigCore KF5::ConfigGui - elisaLib -) + Qt5::Quick Qt5::Widgets + KF5::ConfigCore KF5::ConfigGui + elisaLib + ) if (KF5FileMetaData_FOUND) target_link_libraries(elisaqmlplugin LINK_PRIVATE - KF5::FileMetaData - ) + KF5::FileMetaData + ) endif() set_target_properties(elisaqmlplugin PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/org/kde/elisa -) + ) if (NOT APPLE AND NOT WIN32) set_target_properties(elisaqmlplugin PROPERTIES INSTALL_RPATH "${KDE_INSTALL_FULL_LIBDIR}/elisa;${CMAKE_INSTALL_RPATH}" - ) + ) endif() install(TARGETS elisaqmlplugin DESTINATION ${QML_INSTALL_DIR}/org/kde/elisa/) install(FILES qmldir DESTINATION ${QML_INSTALL_DIR}/org/kde/elisa) add_custom_target(copy) add_custom_target(copy2) file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/bin/org/kde/elisa) add_custom_command(TARGET copy PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/qmldir ${CMAKE_BINARY_DIR}/bin/org/kde/elisa/) add_custom_command(TARGET copy2 PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/plugins.qmltypes ${CMAKE_BINARY_DIR}/bin/org/kde/elisa/) add_dependencies(elisaqmlplugin copy copy2) if (Qt5Quick_FOUND AND Qt5Widgets_FOUND) set(elisa_SOURCES main.cpp windows/WindowsTheme.qml windows/PlatformIntegration.qml android/ElisaMainWindow.qml android/AndroidTheme.qml android/PlatformIntegration.qml + android/AlbumsView.qml + android/ArtistsView.qml + android/TracksView.qml + android/GenresView.qml qml/ElisaMainWindow.qml qml/ApplicationMenu.qml qml/BaseTheme.qml qml/Theme.qml qml/PlatformIntegration.qml qml/LabelWithToolTip.qml qml/RatingStar.qml qml/PlayListEntry.qml qml/MediaBrowser.qml qml/DraggableItem.qml qml/PassiveNotification.qml qml/TopNotification.qml qml/TopNotificationItem.qml qml/TrackImportNotification.qml qml/HeaderBar.qml qml/NavigationActionBar.qml qml/MediaPlayerControl.qml qml/ContextView.qml qml/ContentView.qml qml/ViewSelector.qml qml/ViewManager.qml qml/AlbumsView.qml qml/ArtistsView.qml qml/GenresView.qml qml/TracksView.qml qml/AlbumView.qml qml/OneArtistView.qml qml/OneGenreView.qml qml/MediaPlayListView.qml qml/MediaTrackDelegate.qml qml/MediaAlbumTrackDelegate.qml qml/MediaTrackMetadataView.qml qml/GridBrowserView.qml qml/GridBrowserDelegate.qml qml/ListBrowserView.qml qml/FileBrowserDelegate.qml qml/FileBrowserView.qml qml/ScrollHelper.qml qml/FlatButtonWithToolTip.qml + + qml/AlbumsView.qml + qml/ArtistsView.qml + qml/TracksView.qml + qml/GenresView.qml ) qt5_add_resources(elisa_SOURCES resources.qrc) - set_property(SOURCE qrc_resources.cpp PROPERTY SKIP_AUTOMOC ON) + set_property(SOURCE qrc_resources.cpp PROPERTY SKIP_AUTOMOC ON) set(elisa_ICONS_PNG ../icons/128-apps-elisa.png ../icons/64-apps-elisa.png ../icons/48-apps-elisa.png ../icons/32-apps-elisa.png ../icons/22-apps-elisa.png ../icons/16-apps-elisa.png - ) + ) # add icons to application sources, to have them bundled ecm_add_app_icon(elisa_SOURCES ICONS ${elisa_ICONS_PNG}) add_executable(elisa ${elisa_SOURCES}) target_include_directories(elisa PRIVATE ${KDSoap_INCLUDE_DIRS}) target_link_libraries(elisa LINK_PRIVATE - elisaLib - Qt5::Widgets Qt5::QuickControls2 Qt5::Svg - KF5::I18n KF5::CoreAddons KF5::ConfigCore KF5::ConfigGui - ) + elisaLib + Qt5::Widgets Qt5::QuickControls2 + KF5::I18n KF5::CoreAddons KF5::ConfigCore KF5::ConfigGui + ) if (ANDROID) target_link_libraries(elisa LINK_PRIVATE - Qt5::AndroidExtras + Qt5::AndroidExtras Qt5::Svg Qt5::Sql Qt5::Concurrent KF5::Kirigami2 ) endif() if (KF5Crash_FOUND) target_link_libraries(elisa LINK_PRIVATE KF5::Crash - ) + ) endif() if (KF5Declarative_FOUND) target_link_libraries(elisa LINK_PRIVATE - KF5::Declarative - ) + KF5::Declarative + ) endif() if (NOT APPLE AND NOT WIN32) set_target_properties(elisa PROPERTIES INSTALL_RPATH "${KDE_INSTALL_FULL_LIBDIR}/elisa;${CMAKE_INSTALL_RPATH}" - ) + ) endif() install(TARGETS elisa ${INSTALL_TARGETS_DEFAULT_ARGS}) endif() if (KF5ConfigWidgets_FOUND AND KF5Declarative_FOUND) add_subdirectory(localFileConfiguration) endif() set(elisaImport_SOURCES elisaimport.cpp elisaimportapplication.cpp -) + ) kconfig_add_kcfg_files(elisaImport_SOURCES ../src/elisa_settings.kcfgc ) set(elisaImport_SOURCES ${elisaImport_SOURCES} ../src/elisa_core.kcfg -) + ) add_executable(elisaImport ${elisaImport_SOURCES}) target_link_libraries(elisaImport LINK_PRIVATE - KF5::ConfigCore KF5::ConfigGui - elisaLib -) + KF5::ConfigCore KF5::ConfigGui + elisaLib + ) if (KF5FileMetaData_FOUND) target_link_libraries(elisaImport LINK_PRIVATE - KF5::FileMetaData - ) + KF5::FileMetaData + ) endif() set(QML_IMPORT_PATH ${CMAKE_BINARY_DIR}/bin CACHE INTERNAL "qml import path" FORCE) + +if (ANDROID) + kirigami_package_breeze_icons(ICONS elisa) +endif() diff --git a/src/android/AlbumsView.qml b/src/android/AlbumsView.qml new file mode 100644 index 00000000..7f60367b --- /dev/null +++ b/src/android/AlbumsView.qml @@ -0,0 +1,73 @@ +/* + * Copyright 2018 Matthieu Gallien + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.11 +import QtQuick.Controls 2.4 +import org.kde.kirigami 2.5 as Kirigami +import org.kde.elisa 1.0 + +Kirigami.Page { + id: pageElement + + property bool isSubPage: false + property string mainTitle + property string secondaryTitle + property url image + property var contentModel: elisa.allAlbumsProxyModel + property bool showRating: false + property bool delegateDisplaySecondaryText: true + property bool expandedFilterView: false + property var stackView + + title: i18nc("Title of the view of all albums", "Albums") + + GridView { + id: gridView + + focus: true + anchors.fill: parent + + cellWidth: gridView.width / 2 + cellHeight: gridView.width / 2 + + model: contentModel + + delegate: GridBrowserDelegate { + width: gridView.cellWidth + height: gridView.cellHeight + + focus: true + + isPartial: false + + mainText: model.display + secondaryText: if (pageElement.delegateDisplaySecondaryText) {model.secondaryText} else {""} + imageUrl: model.imageUrl + shadowForImage: if (model.shadowForImage) {model.shadowForImage} else {false} + containerData: model.containerData + delegateDisplaySecondaryText: pageElement.delegateDisplaySecondaryText + + onEnqueue: elisa.mediaPlayList.enqueue(data) + onReplaceAndPlay: elisa.mediaPlayList.replaceAndPlay(data) + onOpen: pageElement.open(model.display, model.secondaryText, model.imageUrl, model.databaseId) + onSelected: { + forceActiveFocus() + contentDirectoryView.currentIndex = model.index + } + } + } +} diff --git a/src/android/AndroidTheme.qml b/src/android/AndroidTheme.qml index 689a98e5..de45a3a4 100644 --- a/src/android/AndroidTheme.qml +++ b/src/android/AndroidTheme.qml @@ -1,33 +1,35 @@ /* * Copyright 2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ -import QtQuick 2.0 +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import org.kde.elisa 1.0 -Item { +BaseTheme { property string albumCover: 'media-optical-audio.svg' property string artistImage: 'view-media-artist.svg' property string clearIcon: 'edit-clear.svg' property string skipBackwardIcon: 'media-skip-backward.svg' property string pauseIcon: 'media-playback-pause.svg' property string playIcon: 'media-playback-start.svg' property string skipForwardIcon: 'media-skip-forward.svg' property string playerVolumeMutedIcon: 'player-volume-muted.svg' property string playerVolumeIcon: 'player-volume.svg' property string ratingIcon: 'rating.svg' property string ratingUnratedIcon: 'rating-unrated.svg' property string errorIcon: 'error.svg' } diff --git a/src/android/ArtistsView.qml b/src/android/ArtistsView.qml new file mode 100644 index 00000000..04518268 --- /dev/null +++ b/src/android/ArtistsView.qml @@ -0,0 +1,73 @@ +/* + * Copyright 2018 Matthieu Gallien + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.11 +import QtQuick.Controls 2.4 +import org.kde.kirigami 2.5 as Kirigami +import org.kde.elisa 1.0 + +Kirigami.Page { + id: pageElement + + property bool isSubPage: false + property string mainTitle + property string secondaryTitle + property url image + property var contentModel: elisa.allArtistsProxyModel + property bool showRating: false + property bool delegateDisplaySecondaryText: true + property bool expandedFilterView: false + property var stackView + + title: i18nc("Title of the view of all artists", "Artists") + + GridView { + id: gridView + + focus: true + anchors.fill: parent + + cellWidth: gridView.width / 2 + cellHeight: gridView.width / 2 + + model: contentModel + + delegate: GridBrowserDelegate { + width: gridView.cellWidth + height: gridView.cellHeight + + focus: true + + isPartial: false + + mainText: model.display + secondaryText: if (pageElement.delegateDisplaySecondaryText) {model.secondaryText} else {""} + imageUrl: model.imageUrl + shadowForImage: if (model.shadowForImage) {model.shadowForImage} else {false} + containerData: model.containerData + delegateDisplaySecondaryText: pageElement.delegateDisplaySecondaryText + + onEnqueue: elisa.mediaPlayList.enqueue(data) + onReplaceAndPlay: elisa.mediaPlayList.replaceAndPlay(data) + onOpen: pageElement.open(model.display, model.secondaryText, model.imageUrl, model.databaseId) + onSelected: { + forceActiveFocus() + contentDirectoryView.currentIndex = model.index + } + } + } +} diff --git a/src/android/ElisaMainWindow.qml b/src/android/ElisaMainWindow.qml index 4d99fd07..b7d97484 100644 --- a/src/android/ElisaMainWindow.qml +++ b/src/android/ElisaMainWindow.qml @@ -1,562 +1,134 @@ /* - * Copyright 2016-2017 Matthieu Gallien + * Copyright 2016-2018 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ -import QtQuick 2.6 -import QtQuick.Controls 2.0 -import QtQuick.Layouts 1.3 +import QtQuick 2.7 +import QtQuick.Controls 2.4 +import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 -import QtQml.Models 2.1 +import Qt.labs.settings 1.0 +import org.kde.kirigami 2.5 as Kirigami import org.kde.elisa 1.0 -ApplicationWindow { +Kirigami.ApplicationWindow { id: mainWindow visible: true - title: 'Elisa' + globalDrawer: Kirigami.GlobalDrawer { + id: drawer + title: "Elisa" + titleIcon: "elisa" + contentItem.implicitWidth: Math.min (Kirigami.Units.gridUnit * 15, mainWindow.width * 0.8) + } + + LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft + LayoutMirroring.childrenInherit: true + + title: i18n("Elisa") SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } - property string globalBrowseFlag: 'BrowseDirectChildren' - property string globalFilter: '*' - property string globalSortCriteria: '' - - PlatformIntegration { - id: platformInterface + Settings { + id: persistentSettings - playListModel: playListModelItem - playListControler: playListControlerItem - audioPlayerManager: manageAudioPlayer - headerBarManager: myHeaderBarManager - manageMediaPlayerControl: myPlayControlManager + property var playListState - onRaisePlayer: - { - mainWindow.raise() - mainWindow.requestActivate() - } - } + property var audioPlayerState - MusicListenersManager { - id: allListeners - } + property double playControlItemVolume : 100.0 + property bool playControlItemMuted : false - AudioWrapper { - id: audioPlayer + property bool playControlItemRepeat : false + property bool playControlItemShuffle : false - audioRole: Audio.MusicRole + property bool expandedFilterView: false - muted: playControlItem.muted - - volume: playControlItem.volume * 100 - - onVolumeChanged: playControlItem.volume = volume / 100.0 - onMutedChanged: playControlItem.muted = muted - - source: manageAudioPlayer.playerSource - - onPlaying: { - myPlayControlManager.playerPlaying() - } - - onPaused: { - myPlayControlManager.playerPaused() - } - - onStopped: { - myPlayControlManager.playerStopped() - } + property bool showPlaylist: true } - MediaPlayList { - id: playListModelItem - - persistentState: persistentSettings.playListState - musicListenersManager: allListeners - } - - PlayListControler { - id: playListControlerItem - - playListModel: playListModelItem - - isValidRole: MediaPlayList.IsValidRole - - onPlayListFinished: manageAudioPlayer.playListFinished() - - persistentState: persistentSettings.playListControlerState - - Component.onCompleted: + Connections { + target: Qt.application + onAboutToQuit: { - var d = new Date(); - var n = d.getMilliseconds(); - seedRandomGenerator(n); + persistentSettings.playListState = elisa.mediaPlayList.persistentState; + persistentSettings.audioPlayerState = elisa.audioControl.persistentState } } - ManageHeaderBar { - id: myHeaderBarManager - - playListModel: playListModelItem - currentTrack: playListControlerItem.currentTrack - - artistRole: MediaPlayList.ArtistRole - titleRole: MediaPlayList.TitleRole - albumRole: MediaPlayList.AlbumRole - imageRole: MediaPlayList.ImageRole - albumIdRole: MediaPlayList.AlbumIdRole - 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 - } - - Connections { - target: allListeners - - onAlbumAdded: allAlbumsModel.albumAdded(newAlbum) - } - Connections { - target: allListeners + target: elisa.mediaPlayList - onAlbumRemoved: allAlbumsModel.albumRemoved(removedAlbum) + onPlayListLoadFailed: { + messageNotification.showNotification(i18nc("message of passive notification when playlist load failed", "Load of playlist failed"), 3000) + } } - Connections { - target: allListeners + pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.TabBar - onAlbumModified: allAlbumsModel.albumModified(modifiedAlbum) - } + Component { + id: albumsView - AllArtistsModel { - id: allArtistsModel + AlbumsView { + } } - Connections { - target: allListeners + Component { + id: artistsView - onArtistAdded: allArtistsModel.artistAdded(newArtist) + ArtistsView { + } } - Connections { - target: allListeners + Component { + id: tracksView - onArtistRemoved: allArtistsModel.artistRemoved(removedArtist) + TracksView { + } } - Connections { - target: allListeners + Component { + id: genresView - onArtistModified: allArtistsModel.artistModified(modifiedArtist) + GenresView { + } } - Rectangle { - color: myPalette.base - anchors.fill: parent - - ColumnLayout { - anchors.fill: parent - spacing: 0 - - HeaderBar { - id: headerBar + Component.onCompleted: + { + elisa.initialize() - Layout.preferredHeight: Screen.pixelDensity * 40. - Layout.minimumHeight: Screen.pixelDensity * 40. - Layout.maximumHeight: Screen.pixelDensity * 40. - Layout.fillWidth: true - - tracksCount: myHeaderBarManager.remainingTracks - album: myHeaderBarManager.album - title: myHeaderBarManager.title - artist: myHeaderBarManager.artist - image: myHeaderBarManager.image - - ratingVisible: false - - Rectangle { - anchors.fill: menuButton - - z: 1 - - radius: width / 2 - - color: myPalette.window - } - } - - MediaPlayerControl { - id: playControlItem - - duration: audioPlayer.duration - seekable: audioPlayer.seekable - - volume: persistentSettings.playControlItemVolume - muted: persistentSettings.playControlItemMuted - position: audioPlayer.position - skipBackwardEnabled: myPlayControlManager.skipBackwardControlEnabled - skipForwardEnabled: myPlayControlManager.skipForwardControlEnabled - playEnabled: myPlayControlManager.playControlEnabled - isPlaying: myPlayControlManager.musicPlaying - - Layout.preferredHeight: Screen.pixelDensity * 12. - Layout.minimumHeight: Layout.preferredHeight - Layout.maximumHeight: Layout.preferredHeight - Layout.fillWidth: true - - onSeek: audioPlayer.seek(position) - - onPlay: manageAudioPlayer.playPause() - onPause: manageAudioPlayer.playPause() - - onPlayPrevious: playListControlerItem.skipPreviousTrack() - onPlayNext: playListControlerItem.skipNextTrack() - } - - RowLayout { - Layout.fillHeight: true - Layout.fillWidth: true - spacing: 0 - - Rectangle { - color: myPalette.window - - Layout.fillHeight: true - Layout.preferredWidth: Screen.pixelDensity * 50. - Layout.minimumWidth: Screen.pixelDensity * 50. - Layout.maximumWidth: Screen.pixelDensity * 50. - } - - Item { - Layout.fillHeight: true - Layout.fillWidth: true - - RowLayout { - anchors.fill: parent - spacing: 0 - - id: contentZone - - Item { - id: mainContentView - - Layout.leftMargin: Screen.pixelDensity * 0.5 - Layout.rightMargin: Screen.pixelDensity * 0.5 - - Layout.fillHeight: true - - Layout.minimumWidth: 0 - Layout.maximumWidth: 0 - Layout.preferredWidth: 0 - - //z: 1 - visible: Layout.minimumWidth != 0 - - 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 - } - } - - Rectangle { - id: firstViewSeparatorItem - - border.width: 1 - border.color: myPalette.mid - color: myPalette.mid - visible: true - - Layout.bottomMargin: Screen.pixelDensity * 0.5 - Layout.topMargin: Screen.pixelDensity * 0.5 - - Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter - - Layout.preferredHeight: parent.height - Screen.pixelDensity * 5. - Layout.minimumHeight: parent.height - Screen.pixelDensity * 5. - Layout.maximumHeight: parent.height - Screen.pixelDensity * 5. - - 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.alignment: Qt.AlignVCenter | Qt.AlignHCenter - - Layout.preferredHeight: parent.height - Screen.pixelDensity * 5. - - 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 - } - } + if (persistentSettings.playListState) { + elisa.mediaPlayList.persistentState = persistentSettings.playListState + } - states: [ - State { - name: 'allAlbums' - when: viewModeView.currentRow === 1 - PropertyChanges { - target: mainContentView - Layout.minimumWidth: contentZone.width * 0.66 - Layout.maximumWidth: contentZone.width * 0.66 - Layout.preferredWidth: 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 - } - }, - State { - name: 'allArtists' - when: viewModeView.currentRow === 2 - PropertyChanges { - target: mainContentView - Layout.minimumWidth: contentZone.width * 0.66 - Layout.maximumWidth: contentZone.width * 0.66 - Layout.preferredWidth: 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 - } - }, - State { - name: 'full' - when: viewModeView.currentRow == 0 - PropertyChanges { - target: mainContentView - 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 - } - } - ] - transitions: Transition { - NumberAnimation { - properties: "Layout.minimumWidth, Layout.maximumWidth, Layout.preferredWidth, opacity" - easing.type: Easing.InOutQuad - duration: 300 - } - } - } - } + if (persistentSettings.audioPlayerState) { + elisa.audioControl.persistentState = persistentSettings.audioPlayerState } + + pageStack.push(albumsView) + pageStack.push(artistsView) + pageStack.push(tracksView) + pageStack.push(genresView) } } - diff --git a/src/android/GenresView.qml b/src/android/GenresView.qml new file mode 100644 index 00000000..0b628ee6 --- /dev/null +++ b/src/android/GenresView.qml @@ -0,0 +1,73 @@ +/* + * Copyright 2018 Matthieu Gallien + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.11 +import QtQuick.Controls 2.4 +import org.kde.kirigami 2.5 as Kirigami +import org.kde.elisa 1.0 + +Kirigami.Page { + id: pageElement + + property bool isSubPage: false + property string mainTitle + property string secondaryTitle + property url image + property var contentModel: elisa.allGenresProxyModel + property bool showRating: false + property bool delegateDisplaySecondaryText: true + property bool expandedFilterView: false + property var stackView + + title: i18nc("Title of the view of all genres", "Genres") + + GridView { + id: gridView + + focus: true + anchors.fill: parent + + cellWidth: gridView.width / 2 + cellHeight: gridView.width / 2 + + model: contentModel + + delegate: GridBrowserDelegate { + width: gridView.cellWidth + height: gridView.cellHeight + + focus: true + + isPartial: false + + mainText: model.display + secondaryText: if (pageElement.delegateDisplaySecondaryText) {model.secondaryText} else {""} + imageUrl: model.imageUrl + shadowForImage: if (model.shadowForImage) {model.shadowForImage} else {false} + containerData: model.containerData + delegateDisplaySecondaryText: pageElement.delegateDisplaySecondaryText + + onEnqueue: elisa.mediaPlayList.enqueue(data) + onReplaceAndPlay: elisa.mediaPlayList.replaceAndPlay(data) + onOpen: pageElement.open(model.display, model.secondaryText, model.imageUrl, model.databaseId) + onSelected: { + forceActiveFocus() + contentDirectoryView.currentIndex = model.index + } + } + } +} diff --git a/src/android/TracksView.qml b/src/android/TracksView.qml new file mode 100644 index 00000000..2915f53e --- /dev/null +++ b/src/android/TracksView.qml @@ -0,0 +1,73 @@ +/* + * Copyright 2018 Matthieu Gallien + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.11 +import QtQuick.Controls 2.4 +import org.kde.kirigami 2.5 as Kirigami +import org.kde.elisa 1.0 + +Kirigami.Page { + id: pageElement + + property bool isSubPage: false + property string mainTitle + property string secondaryTitle + property url image + property var contentModel: elisa.allTracksProxyModel + property bool showRating: false + property bool delegateDisplaySecondaryText: true + property bool expandedFilterView: false + property var stackView + + title: i18nc("Title of the view of all tracks", "Tracks") + + GridView { + id: gridView + + focus: true + anchors.fill: parent + + cellWidth: gridView.width / 2 + cellHeight: gridView.width / 2 + + model: contentModel + + delegate: GridBrowserDelegate { + width: gridView.cellWidth + height: gridView.cellHeight + + focus: true + + isPartial: false + + mainText: model.display + secondaryText: if (pageElement.delegateDisplaySecondaryText) {model.secondaryText} else {""} + imageUrl: model.imageUrl + shadowForImage: if (model.shadowForImage) {model.shadowForImage} else {false} + containerData: model.containerData + delegateDisplaySecondaryText: pageElement.delegateDisplaySecondaryText + + onEnqueue: elisa.mediaPlayList.enqueue(data) + onReplaceAndPlay: elisa.mediaPlayList.replaceAndPlay(data) + onOpen: pageElement.open(model.display, model.secondaryText, model.imageUrl, model.databaseId) + onSelected: { + forceActiveFocus() + contentDirectoryView.currentIndex = model.index + } + } + } +} diff --git a/src/android/androidmusiclistener.cpp b/src/android/androidmusiclistener.cpp new file mode 100644 index 00000000..29d2df5f --- /dev/null +++ b/src/android/androidmusiclistener.cpp @@ -0,0 +1,222 @@ +/* + * Copyright 2018 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 "androidmusiclistener.h" + +#include "databaseinterface.h" + +#include +#include + +// C++ code +#include +#include +#include +#include +#include + +class AndroidMusicListenerPrivate +{ +public: + + QString mSourceName; + + QHash mAllFiles; + + QList mNewTracks; + QHash mCovers; + +}; + +static void tracksAndroidScanStarted(JNIEnv */*env*/, jobject /*obj*/) +{ + AndroidMusicListener::currentInstance()->androidMusicTracksScanStarted(); +} + +static void sentMusicFile(JNIEnv */*env*/, jobject /*obj*/, jstring musicFile) +{ + QAndroidJniObject musicFileJavaString(musicFile); + AndroidMusicListener::currentInstance()->newMusicTrack(musicFileJavaString.toString()); +} + +static void tracksAndroidScanFinished(JNIEnv */*env*/, jobject /*obj*/) +{ + AndroidMusicListener::currentInstance()->androidMusicTracksScanFinished(); +} + +static void albumsAndroidScanStarted(JNIEnv */*env*/, jobject /*obj*/) +{ + AndroidMusicListener::currentInstance()->androidMusicAlbumsScanStarted(); +} + +static void sentMusicAlbum(JNIEnv */*env*/, jobject /*obj*/, jstring musicAlbum) +{ + QAndroidJniObject musicAlbumJavaString(musicAlbum); + AndroidMusicListener::currentInstance()->newMusicAlbum(musicAlbumJavaString.toString()); +} + +static void albumsAndroidScanFinished(JNIEnv */*env*/, jobject /*obj*/) +{ + AndroidMusicListener::currentInstance()->androidMusicAlbumsScanFinished(); +} + +AndroidMusicListener::AndroidMusicListener(QObject *parent) : QObject(parent), d(std::make_unique()) +{ + AndroidMusicListener::mCurrentInstance = this; + + d->mSourceName = QStringLiteral("Android"); + + registerNativeMethods(); +} + +DatabaseInterface *AndroidMusicListener::databaseInterface() const +{ + return nullptr; +} + +void AndroidMusicListener::registerNativeMethods() +{ + JNINativeMethod methods[] {{"androidMusicScanTracksStarting", "()V", reinterpret_cast(tracksAndroidScanStarted)}, + {"sendMusicFile", "(Ljava/lang/String;)V", reinterpret_cast(sentMusicFile)}, + {"androidMusicScanTracksFinishing", "()V", reinterpret_cast(tracksAndroidScanFinished)}, + {"androidMusicScanAlbumsStarting", "()V", reinterpret_cast(albumsAndroidScanStarted)}, + {"sendMusicAlbum", "(Ljava/lang/String;)V", reinterpret_cast(sentMusicAlbum)}, + {"androidMusicScanAlbumsFinishing", "()V", reinterpret_cast(albumsAndroidScanFinished)}, + }; + + QAndroidJniObject javaClass("org/kde/elisa/ElisaAndroidMusicScanner"); + QAndroidJniEnvironment env; + jclass objectClass = env->GetObjectClass(javaClass.object()); + env->RegisterNatives(objectClass, + methods, + sizeof(methods) / sizeof(methods[0])); + env->DeleteLocalRef(objectClass); +} + +AndroidMusicListener *AndroidMusicListener::mCurrentInstance = nullptr; + +AndroidMusicListener *AndroidMusicListener::currentInstance() +{ + return mCurrentInstance; +} + +void AndroidMusicListener::androidMusicTracksScanStarted() +{ +} + +void AndroidMusicListener::newMusicTrack(const QString &trackDescription) +{ + auto trackData = trackDescription.split(QStringLiteral("||")); + auto newTrack = MusicAudioTrack{}; + newTrack.setTitle(trackData[1]); + bool conversionOK = false; + if (trackData[2] != QStringLiteral("null")) { + newTrack.setTrackNumber(trackData[2].toInt(&conversionOK)); + if (!conversionOK) { + qInfo() << "newMusicTrack" << trackData[1] << trackData[2]; + } + } + if (trackData[3] != QStringLiteral("null")) { + newTrack.setYear(trackData[3].toInt(&conversionOK)); + if (!conversionOK) { + qInfo() << "newMusicTrack" << trackData[1] << trackData[3]; + } + } + if (trackData[4] != QStringLiteral("null")) { + newTrack.setDuration(QTime::fromMSecsSinceStartOfDay(trackData[4].toInt())); + } + newTrack.setResourceURI(QUrl::fromLocalFile(trackData[5])); + newTrack.setArtist(trackData[6]); + newTrack.setAlbumName(trackData[8]); + newTrack.setComposer(trackData[10]); + + d->mNewTracks.push_back(newTrack); +} + +void AndroidMusicListener::androidMusicTracksScanFinished() +{ +} + +void AndroidMusicListener::androidMusicAlbumsScanStarted() +{ +} + +void AndroidMusicListener::newMusicAlbum(const QString &albumDescription) +{ + auto albumData = albumDescription.split(QStringLiteral("||")); + + if (albumData[2] != QStringLiteral("null)")) { + d->mCovers[albumData[1]] = QUrl::fromLocalFile(albumData[2]); + } +} + +void AndroidMusicListener::androidMusicAlbumsScanFinished() +{ + Q_EMIT tracksList(d->mNewTracks, d->mCovers, d->mSourceName); +} + +void AndroidMusicListener::setDatabaseInterface(DatabaseInterface *model) +{ + if (model) { + connect(this, &AndroidMusicListener::tracksList, model, &DatabaseInterface::insertTracksList); + connect(this, &AndroidMusicListener::removedTracksList, model, &DatabaseInterface::removeTracksList); + connect(this, &AndroidMusicListener::modifyTracksList, model, &DatabaseInterface::modifyTracksList); + connect(this, &AndroidMusicListener::askRestoredTracks, model, &DatabaseInterface::askRestoredTracks); + connect(model, &DatabaseInterface::restoredTracks, this, &AndroidMusicListener::restoredTracks); + } + + Q_EMIT databaseInterfaceChanged(); + + if (model) { + QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection); + } +} + +void AndroidMusicListener::applicationAboutToQuit() +{ +} + +void AndroidMusicListener::quitListener() +{ + applicationAboutToQuit(); + + Q_EMIT clearDatabase(d->mSourceName); +} + +void AndroidMusicListener::restoredTracks(const QString &musicSource, QHash allFiles) +{ + if (d->mSourceName == musicSource) { + d->mAllFiles = allFiles; + + QAndroidJniObject::callStaticMethod("org/kde/elisa/ElisaAndroidMusicScanner", + "listAudioFiles", + "(Landroid/content/Context;)V", + QtAndroid::androidContext().object()); + } +} + +void AndroidMusicListener::init() +{ + Q_EMIT askRestoredTracks(d->mSourceName); +} + +AndroidMusicListener::~AndroidMusicListener() = default; + + +#include "moc_androidmusiclistener.cpp" diff --git a/src/android/androidmusiclistener.h b/src/android/androidmusiclistener.h new file mode 100644 index 00000000..11c406b5 --- /dev/null +++ b/src/android/androidmusiclistener.h @@ -0,0 +1,113 @@ +/* + * Copyright 2018 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 ANDROIDMUSICLISTENER_H +#define ANDROIDMUSICLISTENER_H + +#include "notificationitem.h" + +#include +#include +#include +#include +#include + +#include + +class DatabaseInterface; +class MusicAudioTrack; +class AndroidMusicListenerPrivate; + +class AndroidMusicListener : public QObject +{ + + Q_OBJECT + + Q_PROPERTY(DatabaseInterface* databaseInterface + READ databaseInterface + WRITE setDatabaseInterface + NOTIFY databaseInterfaceChanged) + +public: + + explicit AndroidMusicListener(QObject *parent = nullptr); + + ~AndroidMusicListener() override; + + DatabaseInterface* databaseInterface() const; + + static void registerNativeMethods(); + + static AndroidMusicListener* currentInstance(); + + void androidMusicTracksScanStarted(); + + void newMusicTrack(const QString &trackDescription); + + void androidMusicTracksScanFinished(); + + void androidMusicAlbumsScanStarted(); + + void newMusicAlbum(const QString &albumDescription); + + void androidMusicAlbumsScanFinished(); + +Q_SIGNALS: + + void databaseInterfaceChanged(); + + void indexingStarted(); + + void indexingFinished(); + + void clearDatabase(const QString &listenerName); + + void newNotification(NotificationItem notification); + + void closeNotification(QString notificationId); + + void tracksList(const QList &tracks, const QHash &covers, const QString &musicSource); + + void removedTracksList(const QList &removedTracks); + + void modifyTracksList(const QList &modifiedTracks, const QHash &covers, const QString &musicSource); + + void askRestoredTracks(const QString &musicSource); + +public Q_SLOTS: + + void setDatabaseInterface(DatabaseInterface* databaseInterface); + + void applicationAboutToQuit(); + + void quitListener(); + + void restoredTracks(const QString &musicSource, QHash allFiles); + + void init(); + +private: + + static AndroidMusicListener* mCurrentInstance; + + std::unique_ptr d; + +}; + +#endif // ANDROIDMUSICLISTENER_H diff --git a/src/databaseinterface.cpp b/src/databaseinterface.cpp index c4f9fd96..efd3105b 100644 --- a/src/databaseinterface.cpp +++ b/src/databaseinterface.cpp @@ -1,5073 +1,5078 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "databaseinterface.h" #include "musicaudiotrack.h" #include +#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), mSelectTracksFromArtist(mTracksDatabase), mSelectTrackFromIdQuery(mTracksDatabase), mSelectCountAlbumsForArtistQuery(mTracksDatabase), mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery(mTracksDatabase), mSelectAllAlbumsQuery(mTracksDatabase), mSelectAllAlbumsFromArtistQuery(mTracksDatabase), mSelectAllArtistsQuery(mTracksDatabase), mInsertArtistsQuery(mTracksDatabase), mSelectArtistByNameQuery(mTracksDatabase), mSelectArtistQuery(mTracksDatabase), mSelectTrackFromFilePathQuery(mTracksDatabase), mRemoveTrackQuery(mTracksDatabase), mRemoveAlbumQuery(mTracksDatabase), mRemoveArtistQuery(mTracksDatabase), mSelectAllTracksQuery(mTracksDatabase), mInsertTrackMapping(mTracksDatabase), mSelectAllTracksFromSourceQuery(mTracksDatabase), mInsertMusicSource(mTracksDatabase), mSelectMusicSource(mTracksDatabase), mUpdateTrackMapping(mTracksDatabase), mSelectTracksMapping(mTracksDatabase), mSelectTracksMappingPriority(mTracksDatabase), mUpdateAlbumArtUriFromAlbumIdQuery(mTracksDatabase), mSelectTracksMappingPriorityByTrackId(mTracksDatabase), mSelectAllTrackFilesFromSourceQuery(mTracksDatabase), mSelectAlbumIdsFromArtist(mTracksDatabase), mRemoveTracksMappingFromSource(mTracksDatabase), mRemoveTracksMapping(mTracksDatabase), mSelectTracksWithoutMappingQuery(mTracksDatabase), mSelectAlbumIdFromTitleAndArtistQuery(mTracksDatabase), mSelectAlbumIdFromTitleWithoutArtistQuery(mTracksDatabase), mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery(mTracksDatabase), mSelectAlbumArtUriFromAlbumIdQuery(mTracksDatabase), mInsertComposerQuery(mTracksDatabase), mSelectComposerByNameQuery(mTracksDatabase), mSelectComposerQuery(mTracksDatabase), mInsertLyricistQuery(mTracksDatabase), mSelectLyricistByNameQuery(mTracksDatabase), mSelectLyricistQuery(mTracksDatabase), mInsertGenreQuery(mTracksDatabase), mSelectGenreByNameQuery(mTracksDatabase), mSelectGenreQuery(mTracksDatabase), mSelectAllTracksShortQuery(mTracksDatabase), mSelectAllAlbumsShortQuery(mTracksDatabase), mSelectAllComposersQuery(mTracksDatabase), mSelectAllLyricistsQuery(mTracksDatabase), mSelectCountAlbumsForComposerQuery(mTracksDatabase), mSelectCountAlbumsForLyricistQuery(mTracksDatabase), mSelectAllGenresQuery(mTracksDatabase), mSelectGenreForArtistQuery(mTracksDatabase), mSelectGenreForAlbumQuery(mTracksDatabase), mUpdateTrackQuery(mTracksDatabase), mUpdateAlbumArtistQuery(mTracksDatabase), mUpdateAlbumArtistInTracksQuery(mTracksDatabase), mQueryMaximumTrackIdQuery(mTracksDatabase), mQueryMaximumAlbumIdQuery(mTracksDatabase), mQueryMaximumArtistIdQuery(mTracksDatabase), mQueryMaximumLyricistIdQuery(mTracksDatabase), mQueryMaximumComposerIdQuery(mTracksDatabase), mQueryMaximumGenreIdQuery(mTracksDatabase), mSelectAllArtistsWithGenreFilterQuery(mTracksDatabase), mSelectAllAlbumsShortWithGenreArtistFilterQuery(mTracksDatabase), mSelectAllAlbumsShortWithArtistFilterQuery(mTracksDatabase) { } QSqlDatabase mTracksDatabase; QSqlQuery mSelectAlbumQuery; QSqlQuery mSelectTrackQuery; QSqlQuery mSelectAlbumIdFromTitleQuery; QSqlQuery mInsertAlbumQuery; QSqlQuery mSelectTrackIdFromTitleAlbumIdArtistQuery; QSqlQuery mInsertTrackQuery; QSqlQuery mSelectTracksFromArtist; QSqlQuery mSelectTrackFromIdQuery; QSqlQuery mSelectCountAlbumsForArtistQuery; QSqlQuery mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery; QSqlQuery mSelectAllAlbumsQuery; QSqlQuery mSelectAllAlbumsFromArtistQuery; QSqlQuery mSelectAllArtistsQuery; QSqlQuery mInsertArtistsQuery; QSqlQuery mSelectArtistByNameQuery; QSqlQuery mSelectArtistQuery; QSqlQuery mSelectTrackFromFilePathQuery; QSqlQuery mRemoveTrackQuery; QSqlQuery mRemoveAlbumQuery; QSqlQuery mRemoveArtistQuery; QSqlQuery mSelectAllTracksQuery; QSqlQuery mInsertTrackMapping; QSqlQuery mSelectAllTracksFromSourceQuery; QSqlQuery mInsertMusicSource; QSqlQuery mSelectMusicSource; QSqlQuery mUpdateTrackMapping; QSqlQuery mSelectTracksMapping; QSqlQuery mSelectTracksMappingPriority; QSqlQuery mUpdateAlbumArtUriFromAlbumIdQuery; QSqlQuery mSelectTracksMappingPriorityByTrackId; QSqlQuery mSelectAllTrackFilesFromSourceQuery; QSqlQuery mSelectAlbumIdsFromArtist; QSqlQuery mRemoveTracksMappingFromSource; QSqlQuery mRemoveTracksMapping; QSqlQuery mSelectTracksWithoutMappingQuery; QSqlQuery mSelectAlbumIdFromTitleAndArtistQuery; QSqlQuery mSelectAlbumIdFromTitleWithoutArtistQuery; QSqlQuery mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery; QSqlQuery mSelectAlbumArtUriFromAlbumIdQuery; QSqlQuery mInsertComposerQuery; QSqlQuery mSelectComposerByNameQuery; QSqlQuery mSelectComposerQuery; QSqlQuery mInsertLyricistQuery; QSqlQuery mSelectLyricistByNameQuery; QSqlQuery mSelectLyricistQuery; QSqlQuery mInsertGenreQuery; QSqlQuery mSelectGenreByNameQuery; QSqlQuery mSelectGenreQuery; QSqlQuery mSelectAllTracksShortQuery; QSqlQuery mSelectAllAlbumsShortQuery; QSqlQuery mSelectAllComposersQuery; QSqlQuery mSelectAllLyricistsQuery; QSqlQuery mSelectCountAlbumsForComposerQuery; QSqlQuery mSelectCountAlbumsForLyricistQuery; QSqlQuery mSelectAllGenresQuery; QSqlQuery mSelectGenreForArtistQuery; QSqlQuery mSelectGenreForAlbumQuery; QSqlQuery mUpdateTrackQuery; QSqlQuery mUpdateAlbumArtistQuery; QSqlQuery mUpdateAlbumArtistInTracksQuery; QSqlQuery mQueryMaximumTrackIdQuery; QSqlQuery mQueryMaximumAlbumIdQuery; QSqlQuery mQueryMaximumArtistIdQuery; QSqlQuery mQueryMaximumLyricistIdQuery; QSqlQuery mQueryMaximumComposerIdQuery; QSqlQuery mQueryMaximumGenreIdQuery; QSqlQuery mSelectAllArtistsWithGenreFilterQuery; QSqlQuery mSelectAllAlbumsShortWithGenreArtistFilterQuery; QSqlQuery mSelectAllAlbumsShortWithArtistFilterQuery; QSet mModifiedTrackIds; QSet mModifiedAlbumIds; QSet mModifiedArtistIds; QSet mInsertedTracks; QSet mInsertedAlbums; QSet mInsertedArtists; qulonglong mAlbumId = 1; qulonglong mArtistId = 1; qulonglong mComposerId = 1; qulonglong mLyricistId = 1; qulonglong mGenreId = 1; qulonglong mTrackId = 1; qulonglong mDiscoverId = 1; QAtomicInt mStopRequest = 0; bool mInitFinished = false; }; DatabaseInterface::DatabaseInterface(QObject *parent) : QObject(parent), d(nullptr) { } DatabaseInterface::~DatabaseInterface() { if (d) { d->mTracksDatabase.close(); } } void DatabaseInterface::init(const QString &dbName, const QString &databaseFileName) { + qInfo() << QCoreApplication::libraryPaths(); 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"); tracksDatabase.exec(QStringLiteral("PRAGMA foreign_keys = ON;")); d = std::make_unique(tracksDatabase); initDatabase(); initRequest(); if (!databaseFileName.isEmpty()) { reloadExistingDatabase(); } } qulonglong DatabaseInterface::albumIdFromTitleAndArtist(const QString &title, const QString &artist) { auto result = qulonglong{0}; auto transactionResult = startTransaction(); if (!transactionResult) { return result; } result = internalAlbumIdFromTitleAndArtist(title, artist); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } DatabaseInterface::ListTrackDataType DatabaseInterface::allTracksData() { auto result = ListTrackDataType{}; if (!d) { return result; } auto transactionResult = startTransaction(); if (!transactionResult) { return result; } result = internalAllTracksPartialData(); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } DatabaseInterface::ListAlbumDataType DatabaseInterface::allAlbumsData() { auto result = ListAlbumDataType{}; if (!d) { return result; } auto transactionResult = startTransaction(); if (!transactionResult) { return result; } result = internalAllAlbumsPartialData(d->mSelectAllAlbumsShortQuery); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } DatabaseInterface::ListAlbumDataType DatabaseInterface::allAlbumsDataByGenreAndArtist(const QString &genre, const QString &artist) { auto result = ListAlbumDataType{}; if (!d) { return result; } auto transactionResult = startTransaction(); if (!transactionResult) { return result; } d->mSelectAllAlbumsShortWithGenreArtistFilterQuery.bindValue(QStringLiteral(":artistFilter"), artist); d->mSelectAllAlbumsShortWithGenreArtistFilterQuery.bindValue(QStringLiteral(":genreFilter"), genre); result = internalAllAlbumsPartialData(d->mSelectAllAlbumsShortWithGenreArtistFilterQuery); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } DatabaseInterface::ListAlbumDataType DatabaseInterface::allAlbumsDataByArtist(const QString &artist) { auto result = ListAlbumDataType{}; if (!d) { return result; } auto transactionResult = startTransaction(); if (!transactionResult) { return result; } d->mSelectAllAlbumsShortWithArtistFilterQuery.bindValue(QStringLiteral(":artistFilter"), artist); result = internalAllAlbumsPartialData(d->mSelectAllAlbumsShortWithArtistFilterQuery); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } DatabaseInterface::AlbumDataType DatabaseInterface::albumDataFromDatabaseId(qulonglong id) { auto result = DatabaseInterface::AlbumDataType{}; if (!d) { return result; } auto transactionResult = startTransaction(); if (!transactionResult) { return result; } result = internalOneAlbumPartialData(id); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } DatabaseInterface::ListTrackDataType DatabaseInterface::albumData(qulonglong databaseId) { auto result = ListTrackDataType{}; if (!d) { return result; } auto transactionResult = startTransaction(); if (!transactionResult) { return result; } d->mSelectTrackQuery.bindValue(QStringLiteral(":albumId"), databaseId); auto queryResult = d->mSelectTrackQuery.exec(); if (!queryResult || !d->mSelectTrackQuery.isSelect() || !d->mSelectTrackQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::albumData" << d->mSelectTrackQuery.lastQuery(); qDebug() << "DatabaseInterface::albumData" << d->mSelectTrackQuery.boundValues(); qDebug() << "DatabaseInterface::albumData" << d->mSelectTrackQuery.lastError(); } while (d->mSelectTrackQuery.next()) { const auto ¤tRecord = d->mSelectTrackQuery.record(); result.push_back(buildTrackDataFromDatabaseRecord(currentRecord)); } d->mSelectTrackQuery.finish(); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } DatabaseInterface::ListArtistDataType DatabaseInterface::allArtistsData() { auto result = ListArtistDataType{}; if (!d) { return result; } auto transactionResult = startTransaction(); if (!transactionResult) { return result; } result = internalAllArtistsPartialData(d->mSelectAllArtistsQuery); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } DatabaseInterface::ListArtistDataType DatabaseInterface::allArtistsDataByGenre(const QString &genre) { qDebug() << "DatabaseInterface::allArtistsDataByGenre" << genre; auto result = ListArtistDataType{}; if (!d) { return result; } auto transactionResult = startTransaction(); if (!transactionResult) { return result; } d->mSelectAllArtistsWithGenreFilterQuery.bindValue(QStringLiteral(":genreFilter"), genre); result = internalAllArtistsPartialData(d->mSelectAllArtistsWithGenreFilterQuery); qDebug() << "DatabaseInterface::allArtistsDataByGenre" << result.count(); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } DatabaseInterface::ListGenreDataType DatabaseInterface::allGenresData() { auto result = ListGenreDataType{}; if (!d) { return result; } auto transactionResult = startTransaction(); if (!transactionResult) { return result; } result = internalAllGenresPartialData(); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } DatabaseInterface::DataType DatabaseInterface::oneData(ElisaUtils::PlayListEntryType aType, qulonglong databaseId) { auto result = DataType{}; if (!d) { return result; } auto transactionResult = startTransaction(); if (!transactionResult) { return result; } switch (aType) { case ElisaUtils::Artist: result = internalOneArtistPartialData(databaseId); break; case ElisaUtils::Album: result = internalOneAlbumPartialData(databaseId); break; case ElisaUtils::Track: result = internalOneTrackPartialData(databaseId); break; case ElisaUtils::Genre: result = internalOneGenrePartialData(databaseId); break; case ElisaUtils::Composer: result = internalOneComposerPartialData(databaseId); break; case ElisaUtils::Lyricist: result = internalOneLyricistPartialData(databaseId); break; case ElisaUtils::Unknown: case ElisaUtils::FileName: break; }; transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } DatabaseInterface::ListTrackDataType DatabaseInterface::tracksDataFromAuthor(const QString &ArtistName) { auto allTracks = ListTrackDataType{}; auto transactionResult = startTransaction(); if (!transactionResult) { return allTracks; } allTracks = internalTracksFromAuthor(ArtistName); transactionResult = finishTransaction(); if (!transactionResult) { return allTracks; } return allTracks; } DatabaseInterface::TrackDataType DatabaseInterface::trackDataFromDatabaseId(qulonglong id) { auto result = TrackDataType(); if (!d) { return result; } auto transactionResult = startTransaction(); if (!transactionResult) { return result; } result = internalOneTrackPartialData(id); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } qulonglong DatabaseInterface::trackIdFromTitleAlbumTrackDiscNumber(const QString &title, const QString &artist, const QString &album, int trackNumber, int discNumber) { auto result = qulonglong(0); if (!d) { return result; } auto transactionResult = startTransaction(); if (!transactionResult) { return result; } result = internalTrackIdFromTitleAlbumTracDiscNumber(title, artist, album, trackNumber, discNumber); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } qulonglong DatabaseInterface::trackIdFromFileName(const QUrl &fileName) { auto result = qulonglong(0); if (!d) { return result; } auto transactionResult = startTransaction(); if (!transactionResult) { return result; } result = internalTrackIdFromFileName(fileName); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } void DatabaseInterface::applicationAboutToQuit() { d->mStopRequest = 1; } void DatabaseInterface::removeAllTracksFromSource(const QString &sourceName) { auto transactionResult = startTransaction(); if (!transactionResult) { return; } initChangesTrackers(); auto sourceId = internalSourceIdFromName(sourceName); auto allFileNames = internalAllFileNameFromSource(sourceId); internalRemoveTracksList(allFileNames, sourceId); if (!d->mInsertedArtists.isEmpty()) { ListArtistDataType newArtists; for (auto artistId : qAsConst(d->mInsertedArtists)) { newArtists.push_back({{DatabaseIdRole, artistId}}); } Q_EMIT artistsAdded(newArtists); } transactionResult = finishTransaction(); if (!transactionResult) { return; } } void DatabaseInterface::askRestoredTracks(const QString &musicSource) { auto transactionResult = startTransaction(); if (!transactionResult) { return; } auto result = internalAllFileNameFromSource(internalSourceIdFromName(musicSource)); Q_EMIT restoredTracks(musicSource, result); transactionResult = finishTransaction(); if (!transactionResult) { return; } } void DatabaseInterface::initChangesTrackers() { d->mModifiedTrackIds.clear(); d->mModifiedAlbumIds.clear(); d->mModifiedArtistIds.clear(); d->mInsertedTracks.clear(); d->mInsertedAlbums.clear(); d->mInsertedArtists.clear(); } void DatabaseInterface::recordModifiedTrack(qulonglong trackId) { d->mModifiedTrackIds.insert(trackId); } void DatabaseInterface::recordModifiedAlbum(qulonglong albumId) { d->mModifiedAlbumIds.insert(albumId); } void DatabaseInterface::insertTracksList(const QList &tracks, const QHash &covers, const QString &musicSource) { if (d->mStopRequest == 1) { return; } auto transactionResult = startTransaction(); if (!transactionResult) { return; } initChangesTrackers(); for(const auto &oneTrack : tracks) { d->mSelectTracksMapping.bindValue(QStringLiteral(":fileName"), oneTrack.resourceURI()); auto result = d->mSelectTracksMapping.exec(); if (!result || !d->mSelectTracksMapping.isSelect() || !d->mSelectTracksMapping.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertTracksList" << d->mSelectTracksMapping.lastQuery(); qDebug() << "DatabaseInterface::insertTracksList" << d->mSelectTracksMapping.boundValues(); qDebug() << "DatabaseInterface::insertTracksList" << d->mSelectTracksMapping.lastError(); d->mSelectTracksMapping.finish(); rollBackTransaction(); return; } bool isNewTrack = !d->mSelectTracksMapping.next(); if (isNewTrack) { insertTrackOrigin(oneTrack.resourceURI(), oneTrack.fileModificationTime(), insertMusicSource(musicSource)); } else if (!d->mSelectTracksMapping.record().value(0).isNull() && d->mSelectTracksMapping.record().value(0).toULongLong() != 0) { updateTrackOrigin(d->mSelectTracksMapping.record().value(0).toULongLong(), oneTrack.resourceURI(), oneTrack.fileModificationTime()); } else { continue; } d->mSelectTracksMapping.finish(); const auto insertedTrackId = internalInsertTrack(oneTrack, covers, 0, (isNewTrack ? TrackFileInsertType::NewTrackFileInsert : TrackFileInsertType::ModifiedTrackFileInsert)); if (isNewTrack && insertedTrackId != 0) { d->mInsertedTracks.insert(insertedTrackId); } if (d->mStopRequest == 1) { transactionResult = finishTransaction(); if (!transactionResult) { return; } return; } } if (!d->mInsertedArtists.isEmpty()) { ListArtistDataType newArtists; for (auto artistId : qAsConst(d->mInsertedArtists)) { newArtists.push_back({{DatabaseIdRole, artistId}}); } - + qInfo() << "artistsAdded" << newArtists.size(); Q_EMIT artistsAdded(newArtists); } if (!d->mInsertedAlbums.isEmpty()) { ListAlbumDataType newAlbums; for (auto albumId : qAsConst(d->mInsertedAlbums)) { d->mModifiedAlbumIds.remove(albumId); newAlbums.push_back(internalOneAlbumPartialData(albumId)); } + qInfo() << "albumsAdded" << newAlbums.size(); Q_EMIT albumsAdded(newAlbums); } for (auto albumId : qAsConst(d->mModifiedAlbumIds)) { Q_EMIT albumModified({{DatabaseIdRole, albumId}}, albumId); } if (!d->mInsertedTracks.isEmpty()) { ListTrackDataType newTracks; for (auto trackId : qAsConst(d->mInsertedTracks)) { newTracks.push_back(internalOneTrackPartialData(trackId)); d->mModifiedTrackIds.remove(trackId); } + qInfo() << "tracksAdded" << newTracks.size(); Q_EMIT tracksAdded(newTracks); } for (auto trackId : qAsConst(d->mModifiedTrackIds)) { Q_EMIT trackModified(internalOneTrackPartialData(trackId)); } transactionResult = finishTransaction(); if (!transactionResult) { return; } } void DatabaseInterface::removeTracksList(const QList &removedTracks) { auto transactionResult = startTransaction(); if (!transactionResult) { return; } initChangesTrackers(); internalRemoveTracksList(removedTracks); if (!d->mInsertedArtists.isEmpty()) { ListArtistDataType newArtists; for (auto artistId : qAsConst(d->mInsertedArtists)) { newArtists.push_back({{DatabaseIdRole, artistId}}); } Q_EMIT artistsAdded(newArtists); } transactionResult = finishTransaction(); if (!transactionResult) { return; } } void DatabaseInterface::modifyTracksList(const QList &modifiedTracks, const QHash &covers, const QString &musicSource) { auto transactionResult = startTransaction(); if (!transactionResult) { return; } initChangesTrackers(); for (const auto &oneModifiedTrack : modifiedTracks) { if (oneModifiedTrack.albumArtist().isEmpty()) { continue; } bool modifyExistingTrack = internalTrackFromDatabaseId(oneModifiedTrack.databaseId()).isValid() || (internalTrackIdFromFileName(oneModifiedTrack.resourceURI()) != 0); auto originTrackId = oneModifiedTrack.databaseId(); if (!originTrackId) { originTrackId = internalTrackIdFromFileName(oneModifiedTrack.resourceURI()); } if (!modifyExistingTrack) { insertTrackOrigin(oneModifiedTrack.resourceURI(), oneModifiedTrack.fileModificationTime(), insertMusicSource(musicSource)); } else { updateTrackOrigin(originTrackId, oneModifiedTrack.resourceURI(), oneModifiedTrack.fileModificationTime()); } const auto insertedTrackId = internalInsertTrack(oneModifiedTrack, covers, (modifyExistingTrack ? originTrackId : 0), (modifyExistingTrack ? TrackFileInsertType::ModifiedTrackFileInsert : TrackFileInsertType::NewTrackFileInsert)); if (!modifyExistingTrack && insertedTrackId != 0) { d->mInsertedTracks.insert(insertedTrackId); } } if (!d->mInsertedArtists.isEmpty()) { ListArtistDataType newArtists; for (auto artistId : qAsConst(d->mInsertedArtists)) { newArtists.push_back({{DatabaseIdRole, artistId}}); } Q_EMIT artistsAdded(newArtists); } if (!d->mInsertedAlbums.isEmpty()) { ListAlbumDataType newAlbums; for (auto albumId : qAsConst(d->mInsertedAlbums)) { d->mModifiedAlbumIds.remove(albumId); newAlbums.push_back(internalOneAlbumPartialData(albumId)); } Q_EMIT albumsAdded(newAlbums); } for (auto albumId : qAsConst(d->mModifiedAlbumIds)) { Q_EMIT albumModified({{DatabaseIdRole, albumId}}, albumId); } if (!d->mInsertedTracks.isEmpty()) { ListTrackDataType newTracks; for (auto trackId : qAsConst(d->mInsertedTracks)) { newTracks.push_back(internalOneTrackPartialData(trackId)); d->mModifiedTrackIds.remove(trackId); } Q_EMIT tracksAdded(newTracks); } for (auto trackId : qAsConst(d->mModifiedTrackIds)) { Q_EMIT trackModified(internalOneTrackPartialData(trackId)); } 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() { auto transactionResult = startTransaction(); if (!transactionResult) { return; } auto listTables = d->mTracksDatabase.tables(); if (!listTables.contains(QStringLiteral("DatabaseVersionV7"))) { auto oldTables = QStringList{ QStringLiteral("DatabaseVersionV2"), QStringLiteral("DatabaseVersionV3"), QStringLiteral("DatabaseVersionV4"), QStringLiteral("DatabaseVersionV5"), QStringLiteral("DatabaseVersionV6"), QStringLiteral("AlbumsArtists"), QStringLiteral("TracksArtists"), QStringLiteral("TracksMapping"), QStringLiteral("Tracks"), QStringLiteral("Composer"), QStringLiteral("Genre"), QStringLiteral("Lyricist"), QStringLiteral("Albums"), QStringLiteral("DiscoverSource"), QStringLiteral("Artists"),}; for (const auto &oneTable : oldTables) { if (listTables.indexOf(oneTable) == -1) { continue; } QSqlQuery createSchemaQuery(d->mTracksDatabase); auto result = createSchemaQuery.exec(QStringLiteral("DROP TABLE ") + oneTable); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastError(); Q_EMIT databaseError(); } } listTables = d->mTracksDatabase.tables(); } if (!listTables.contains(QStringLiteral("DatabaseVersionV7"))) { QSqlQuery createSchemaQuery(d->mTracksDatabase); const auto &result = createSchemaQuery.exec(QStringLiteral("CREATE TABLE `DatabaseVersionV7` (`Version` INTEGER PRIMARY KEY NOT NULL)")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastError(); Q_EMIT databaseError(); } } if (!listTables.contains(QStringLiteral("DiscoverSource"))) { QSqlQuery createSchemaQuery(d->mTracksDatabase); const auto &result = createSchemaQuery.exec(QStringLiteral("CREATE TABLE `DiscoverSource` (`ID` INTEGER PRIMARY KEY NOT NULL, " "`Name` VARCHAR(55) NOT NULL, " "UNIQUE (`Name`))")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastError(); Q_EMIT databaseError(); } } 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(); Q_EMIT databaseError(); } } if (!listTables.contains(QStringLiteral("Composer"))) { QSqlQuery createSchemaQuery(d->mTracksDatabase); const auto &result = createSchemaQuery.exec(QStringLiteral("CREATE TABLE `Composer` (`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(); Q_EMIT databaseError(); } } if (!listTables.contains(QStringLiteral("Genre"))) { QSqlQuery createSchemaQuery(d->mTracksDatabase); const auto &result = createSchemaQuery.exec(QStringLiteral("CREATE TABLE `Genre` (`ID` INTEGER PRIMARY KEY NOT NULL, " "`Name` VARCHAR(85) NOT NULL, " "UNIQUE (`Name`))")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastError(); Q_EMIT databaseError(); } } if (!listTables.contains(QStringLiteral("Lyricist"))) { QSqlQuery createSchemaQuery(d->mTracksDatabase); const auto &result = createSchemaQuery.exec(QStringLiteral("CREATE TABLE `Lyricist` (`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(); Q_EMIT databaseError(); } } 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, " "`ArtistName` VARCHAR(55), " "`AlbumPath` VARCHAR(255) NOT NULL, " "`CoverFileName` VARCHAR(255) NOT NULL, " "`AlbumInternalID` VARCHAR(55), " "UNIQUE (`Title`, `ArtistName`, `AlbumPath`), " "CONSTRAINT fk_artists FOREIGN KEY (`ArtistName`) REFERENCES `Artists`(`Name`) " "ON DELETE CASCADE)")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastError(); } } if (!listTables.contains(QStringLiteral("Tracks"))) { QSqlQuery createSchemaQuery(d->mTracksDatabase); const auto &result = createSchemaQuery.exec(QStringLiteral("CREATE TABLE `Tracks` (" "`ID` INTEGER PRIMARY KEY NOT NULL, " "`Title` VARCHAR(85) NOT NULL, " "`ArtistName` VARCHAR(55), " "`AlbumTitle` VARCHAR(55), " "`AlbumArtistName` VARCHAR(55), " "`AlbumPath` VARCHAR(255), " "`TrackNumber` INTEGER DEFAULT -1, " "`DiscNumber` INTEGER DEFAULT -1, " "`Duration` INTEGER NOT NULL, " "`Rating` INTEGER NOT NULL DEFAULT 0, " "`GenreID` INTEGER DEFAULT -1, " "`ComposerID` INTEGER, " "`LyricistID` INTEGER, " "`Comment` VARCHAR(85) DEFAULT '', " "`Year` INTEGER DEFAULT 0, " "`Channels` INTEGER DEFAULT -1, " "`BitRate` INTEGER DEFAULT -1, " "`SampleRate` INTEGER DEFAULT -1, " "`HasEmbeddedCover` BOOLEAN NOT NULL, " "UNIQUE (" "`Title`, `AlbumTitle`, `AlbumArtistName`, " "`AlbumPath`, `TrackNumber`, `DiscNumber`" "), " "CONSTRAINT fk_artist FOREIGN KEY (`ArtistName`) REFERENCES `Artists`(`Name`), " "CONSTRAINT fk_tracks_composer FOREIGN KEY (`ComposerID`) REFERENCES `Composer`(`ID`), " "CONSTRAINT fk_tracks_lyricist FOREIGN KEY (`LyricistID`) REFERENCES `Lyricist`(`ID`), " "CONSTRAINT fk_tracks_genre FOREIGN KEY (`GenreID`) REFERENCES `Genre`(`ID`), " "CONSTRAINT fk_tracks_album FOREIGN KEY (" "`AlbumTitle`, `AlbumArtistName`, `AlbumPath`)" "REFERENCES `Albums`(`Title`, `ArtistName`, `AlbumPath`))")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastError(); Q_EMIT databaseError(); } } 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, " "`FileModifiedTime` DATETIME NOT NULL, " "PRIMARY KEY (`FileName`), " "CONSTRAINT TracksUnique UNIQUE (`TrackID`, `Priority`), " "CONSTRAINT fk_tracksmapping_trackID FOREIGN KEY (`TrackID`) REFERENCES `Tracks`(`ID`) ON DELETE CASCADE, " "CONSTRAINT fk_tracksmapping_discoverID FOREIGN KEY (`DiscoverID`) REFERENCES `DiscoverSource`(`ID`))")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createSchemaQuery.lastError(); } } { QSqlQuery createTrackIndex(d->mTracksDatabase); const auto &result = createTrackIndex.exec(QStringLiteral("CREATE INDEX " "IF NOT EXISTS " "`TitleAlbumsIndex` ON `Albums` " "(`Title`)")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createTrackIndex.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createTrackIndex.lastError(); Q_EMIT databaseError(); } } { QSqlQuery createTrackIndex(d->mTracksDatabase); const auto &result = createTrackIndex.exec(QStringLiteral("CREATE INDEX " "IF NOT EXISTS " "`ArtistNameAlbumsIndex` ON `Albums` " "(`ArtistName`)")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createTrackIndex.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createTrackIndex.lastError(); Q_EMIT databaseError(); } } { QSqlQuery createTrackIndex(d->mTracksDatabase); const auto &result = createTrackIndex.exec(QStringLiteral("CREATE INDEX " "IF NOT EXISTS " "`TracksAlbumIndex` ON `Tracks` " "(`AlbumTitle`, `AlbumArtistName`, `AlbumPath`)")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createTrackIndex.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createTrackIndex.lastError(); Q_EMIT databaseError(); } } { QSqlQuery createTrackIndex(d->mTracksDatabase); const auto &result = createTrackIndex.exec(QStringLiteral("CREATE INDEX " "IF NOT EXISTS " "`ArtistNameIndex` ON `Tracks` " "(`ArtistName`)")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createTrackIndex.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createTrackIndex.lastError(); Q_EMIT databaseError(); } } { QSqlQuery createTrackIndex(d->mTracksDatabase); const auto &result = createTrackIndex.exec(QStringLiteral("CREATE INDEX " "IF NOT EXISTS " "`AlbumArtistNameIndex` ON `Tracks` " "(`AlbumArtistName`)")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createTrackIndex.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createTrackIndex.lastError(); Q_EMIT databaseError(); } } { QSqlQuery createTrackIndex(d->mTracksDatabase); const auto &result = createTrackIndex.exec(QStringLiteral("CREATE INDEX " "IF NOT EXISTS " "`TracksFileNameIndex` ON `TracksMapping` " "(`FileName`)")); if (!result) { qDebug() << "DatabaseInterface::initDatabase" << createTrackIndex.lastQuery(); qDebug() << "DatabaseInterface::initDatabase" << createTrackIndex.lastError(); Q_EMIT databaseError(); } } transactionResult = finishTransaction(); if (!transactionResult) { return; } } void DatabaseInterface::initRequest() { auto transactionResult = startTransaction(); if (!transactionResult) { return; } { auto selectAlbumQueryText = QStringLiteral("SELECT " "album.`ID`, " "album.`Title`, " "album.`AlbumInternalID`, " "album.`ArtistName`, " "album.`AlbumPath`, " "album.`CoverFileName`, " "(" "SELECT " "COUNT(*) " "FROM " "`Tracks` tracks3 " "WHERE " "tracks3.`AlbumTitle` = album.`Title` AND " "(tracks3.`AlbumArtistName` = album.`ArtistName` OR " "(tracks3.`AlbumArtistName` IS NULL AND " "album.`ArtistName` IS NULL" ")" ") AND " "tracks3.`AlbumPath` = album.`AlbumPath` " ") as `TracksCount`, " "(" "SELECT " "COUNT(DISTINCT tracks2.DiscNumber) <= 1 " "FROM " "`Tracks` tracks2 " "WHERE " "tracks2.`AlbumTitle` = album.`Title` AND " "(tracks2.`AlbumArtistName` = album.`ArtistName` OR " "(tracks2.`AlbumArtistName` IS NULL AND " "album.`ArtistName` IS NULL" ")" ") AND " "tracks2.`AlbumPath` = album.`AlbumPath` " ") as `IsSingleDiscAlbum`, " "GROUP_CONCAT(tracks.`ArtistName`, ', ') as AllArtists, " "MAX(tracks.`Rating`) as HighestRating, " "GROUP_CONCAT(genres.`Name`, ', ') as AllGenres " "FROM " "`Albums` album LEFT JOIN " "`Tracks` tracks ON " "tracks.`AlbumTitle` = album.`Title` AND " "(" "tracks.`AlbumArtistName` = album.`ArtistName` OR " "(" "tracks.`AlbumArtistName` IS NULL AND " "album.`ArtistName` IS NULL" ")" ") AND " "tracks.`AlbumPath` = album.`AlbumPath`" "LEFT JOIN " "`Genre` genres ON tracks.`GenreID` = genres.`ID` " "WHERE " "album.`ID` = :albumId " "GROUP BY album.`ID`"); auto result = prepareQuery(d->mSelectAlbumQuery, selectAlbumQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumQuery.lastError(); Q_EMIT databaseError(); } } { auto selectAllAlbumsText = QStringLiteral("SELECT " "album.`ID`, " "album.`Title`, " "album.`AlbumInternalID`, " "album.`ArtistName`, " "album.`AlbumPath`, " "album.`CoverFileName`, " "(" "SELECT " "COUNT(*) " "FROM " "`Tracks` tracks " "WHERE " "tracks.`AlbumTitle` = album.`Title` AND " "(tracks.`AlbumArtistName` = album.`ArtistName` OR " "(tracks.`AlbumArtistName` IS NULL AND " "album.`ArtistName` IS NULL" ")" ") AND " "tracks.`AlbumPath` = album.`AlbumPath` " ") as `TracksCount`, " "(" "SELECT " "COUNT(DISTINCT tracks2.DiscNumber) <= 1 " "FROM " "`Tracks` tracks2 " "WHERE " "tracks2.`AlbumTitle` = album.`Title` AND " "(tracks2.`AlbumArtistName` = album.`ArtistName` OR " "(tracks2.`AlbumArtistName` IS NULL AND " "album.`ArtistName` IS NULL" ")" ") AND " "tracks2.`AlbumPath` = album.`AlbumPath` " ") as `IsSingleDiscAlbum` " "FROM `Albums` album " "ORDER BY album.`Title` COLLATE NOCASE"); auto result = prepareQuery(d->mSelectAllAlbumsQuery, selectAllAlbumsText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllAlbumsQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllAlbumsQuery.lastError(); Q_EMIT databaseError(); } } { auto selectAllGenresText = QStringLiteral("SELECT " "genre.`ID`, " "genre.`Name` " "FROM `Genre` genre " "ORDER BY genre.`Name` COLLATE NOCASE"); auto result = prepareQuery(d->mSelectAllGenresQuery, selectAllGenresText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllAlbumsQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllAlbumsQuery.lastError(); Q_EMIT databaseError(); } } { auto selectAllAlbumsText = QStringLiteral("SELECT " "album.`ID`, " "album.`Title`, " "album.`ArtistName` as SecondaryText, " "album.`CoverFileName`, " "album.`ArtistName`, " "GROUP_CONCAT(tracks.`ArtistName`, ', ') as AllArtists, " "MAX(tracks.`Rating`) as HighestRating, " "GROUP_CONCAT(genres.`Name`, ', ') as AllGenres, " "(" "SELECT " "COUNT(DISTINCT tracks2.DiscNumber) <= 1 " "FROM " "`Tracks` tracks2 " "WHERE " "tracks2.`AlbumTitle` = album.`Title` AND " "(tracks2.`AlbumArtistName` = album.`ArtistName` OR " "(tracks2.`AlbumArtistName` IS NULL AND " "album.`ArtistName` IS NULL" ")" ") AND " "tracks2.`AlbumPath` = album.`AlbumPath` " ") as `IsSingleDiscAlbum` " "FROM " "`Albums` album, " "`Tracks` tracks LEFT JOIN " "`Genre` genres ON tracks.`GenreID` = genres.`ID` " "WHERE " "tracks.`AlbumTitle` = album.`Title` AND " "(tracks.`AlbumArtistName` = album.`ArtistName` OR " "(tracks.`AlbumArtistName` IS NULL AND " "album.`ArtistName` IS NULL" ")" ") AND " "tracks.`AlbumPath` = album.`AlbumPath` " "GROUP BY album.`ID`, album.`Title`, album.`AlbumPath` " "ORDER BY album.`Title` COLLATE NOCASE"); auto result = prepareQuery(d->mSelectAllAlbumsShortQuery, selectAllAlbumsText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllAlbumsShortQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllAlbumsShortQuery.lastError(); Q_EMIT databaseError(); } } { auto selectAllAlbumsText = QStringLiteral("SELECT " "album.`ID`, " "album.`Title`, " "album.`ArtistName` as SecondaryText, " "album.`CoverFileName`, " "album.`ArtistName`, " "GROUP_CONCAT(tracks.`ArtistName`, ', ') as AllArtists, " "MAX(tracks.`Rating`) as HighestRating, " "GROUP_CONCAT(genres.`Name`, ', ') as AllGenres " "FROM " "`Albums` album, " "`Tracks` tracks LEFT JOIN " "`Genre` genres ON tracks.`GenreID` = genres.`ID` " "WHERE " "tracks.`AlbumTitle` = album.`Title` AND " "(tracks.`AlbumArtistName` = album.`ArtistName` OR " "(tracks.`AlbumArtistName` IS NULL AND " "album.`ArtistName` IS NULL" ")" ") AND " "tracks.`AlbumPath` = album.`AlbumPath` AND " "EXISTS (" " SELECT tracks2.`GenreID` " " FROM " " `Tracks` tracks2, " " `Genre` genre2 " " WHERE " " tracks2.`AlbumTitle` = album.`Title` AND " " tracks2.`AlbumArtistName` = album.`ArtistName` AND " " tracks2.`GenreID` = genre2.`ID` AND " " genre2.`Name` = :genreFilter AND " " (tracks2.`ArtistName` = :artistFilter OR tracks2.`AlbumArtistName` = :artistFilter) " ") " "GROUP BY album.`ID`, album.`Title`, album.`AlbumPath` " "ORDER BY album.`Title` COLLATE NOCASE"); auto result = prepareQuery(d->mSelectAllAlbumsShortWithGenreArtistFilterQuery, selectAllAlbumsText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllAlbumsShortWithGenreArtistFilterQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllAlbumsShortWithGenreArtistFilterQuery.lastError(); Q_EMIT databaseError(); } } { auto selectAllAlbumsText = QStringLiteral("SELECT " "album.`ID`, " "album.`Title`, " "album.`ArtistName` as SecondaryText, " "album.`CoverFileName`, " "album.`ArtistName`, " "GROUP_CONCAT(tracks.`ArtistName`, ', ') as AllArtists, " "MAX(tracks.`Rating`) as HighestRating, " "GROUP_CONCAT(genres.`Name`, ', ') as AllGenres " "FROM " "`Albums` album, " "`Tracks` tracks LEFT JOIN " "`Genre` genres ON tracks.`GenreID` = genres.`ID` " "WHERE " "tracks.`AlbumTitle` = album.`Title` AND " "(tracks.`AlbumArtistName` = album.`ArtistName` OR " "(tracks.`AlbumArtistName` IS NULL AND " "album.`ArtistName` IS NULL" ")" ") AND " "tracks.`AlbumPath` = album.`AlbumPath` AND " "EXISTS (" " SELECT tracks2.`GenreID` " " FROM " " `Tracks` tracks2 " " WHERE " " tracks2.`AlbumTitle` = album.`Title` AND " " tracks2.`AlbumArtistName` = album.`ArtistName` AND " " (tracks2.`ArtistName` = :artistFilter OR tracks2.`AlbumArtistName` = :artistFilter) " ") " "GROUP BY album.`ID`, album.`Title`, album.`AlbumPath` " "ORDER BY album.`Title` COLLATE NOCASE"); auto result = prepareQuery(d->mSelectAllAlbumsShortWithArtistFilterQuery, selectAllAlbumsText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllAlbumsShortWithArtistFilterQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllAlbumsShortWithArtistFilterQuery.lastError(); Q_EMIT databaseError(); } } { auto selectAllArtistsWithFilterText = QStringLiteral("SELECT artists.`ID`, " "artists.`Name`, " "GROUP_CONCAT(genres.`Name`, ', ') as AllGenres " "FROM `Artists` artists LEFT JOIN " "`Tracks` tracks ON artists.`Name` = tracks.`ArtistName` LEFT JOIN " "`Genre` genres ON tracks.`GenreID` = genres.`ID` " "GROUP BY artists.`ID` " "ORDER BY artists.`Name` COLLATE NOCASE"); auto result = prepareQuery(d->mSelectAllArtistsQuery, selectAllArtistsWithFilterText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllArtistsQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllArtistsQuery.lastError(); Q_EMIT databaseError(); } } { auto selectAllArtistsWithGenreFilterText = QStringLiteral("SELECT artists.`ID`, " "artists.`Name`, " "GROUP_CONCAT(genres.`Name`, ', ') as AllGenres " "FROM `Artists` artists LEFT JOIN " "`Tracks` tracks ON (tracks.`ArtistName` = artists.`Name` OR tracks.`AlbumArtistName` = artists.`Name`) LEFT JOIN " "`Genre` genres ON tracks.`GenreID` = genres.`ID` " "WHERE " "EXISTS (" " SELECT tracks2.`GenreID` " " FROM " " `Tracks` tracks2, " " `Genre` genre2 " " WHERE " " (tracks2.`ArtistName` = artists.`Name` OR tracks2.`AlbumArtistName` = artists.`Name`) AND " " tracks2.`GenreID` = genre2.`ID` AND " " genre2.`Name` = :genreFilter " ") " "GROUP BY artists.`ID` " "ORDER BY artists.`Name` COLLATE NOCASE"); auto result = prepareQuery(d->mSelectAllArtistsWithGenreFilterQuery, selectAllArtistsWithGenreFilterText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllArtistsWithGenreFilterQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllArtistsWithGenreFilterQuery.lastError(); Q_EMIT databaseError(); } } { auto selectAllComposersWithFilterText = QStringLiteral("SELECT `ID`, " "`Name` " "FROM `Artists` " "ORDER BY `Name` COLLATE NOCASE"); auto result = prepareQuery(d->mSelectAllComposersQuery, selectAllComposersWithFilterText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllComposersQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllComposersQuery.lastError(); Q_EMIT databaseError(); } } { auto selectAllLyricistsWithFilterText = QStringLiteral("SELECT `ID`, " "`Name` " "FROM `Lyricist` " "ORDER BY `Name` COLLATE NOCASE"); auto result = prepareQuery(d->mSelectAllLyricistsQuery, selectAllLyricistsWithFilterText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllLyricistsQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllLyricistsQuery.lastError(); Q_EMIT databaseError(); } } { auto selectAllTracksText = QStringLiteral("SELECT " "tracks.`ID`, " "tracks.`Title`, " "album.`ID`, " "tracks.`ArtistName`, " "tracks.`AlbumArtistName`, " "tracksMapping.`FileName`, " "tracksMapping.`FileModifiedTime`, " "tracks.`TrackNumber`, " "tracks.`DiscNumber`, " "tracks.`Duration`, " "tracks.`AlbumTitle`, " "tracks.`Rating`, " "album.`CoverFileName`, " "(" "SELECT " "COUNT(DISTINCT tracks2.DiscNumber) <= 1 " "FROM " "`Tracks` tracks2 " "WHERE " "tracks2.`AlbumTitle` = album.`Title` AND " "(tracks2.`AlbumArtistName` = album.`ArtistName` OR " "(tracks2.`AlbumArtistName` IS NULL AND " "album.`ArtistName` IS NULL" ")" ") AND " "tracks2.`AlbumPath` = album.`AlbumPath` " ") as `IsSingleDiscAlbum`, " "trackGenre.`Name`, " "trackComposer.`Name`, " "trackLyricist.`Name`, " "tracks.`Comment`, " "tracks.`Year`, " "tracks.`Channels`, " "tracks.`BitRate`, " "tracks.`SampleRate`, " "tracks.`HasEmbeddedCover` " "FROM " "`Tracks` tracks, " "`TracksMapping` tracksMapping " "LEFT JOIN " "`Albums` album " "ON " "tracks.`AlbumTitle` = album.`Title` AND " "(tracks.`AlbumArtistName` = album.`ArtistName` OR tracks.`AlbumArtistName` IS NULL ) AND " "tracks.`AlbumPath` = album.`AlbumPath` " "LEFT JOIN `Genre` trackGenre ON trackGenre.`ID` = tracks.`GenreID` " "LEFT JOIN `Composer` trackComposer ON trackComposer.`ID` = tracks.`ComposerID` " "LEFT JOIN `Lyricist` trackLyricist ON trackLyricist.`ID` = tracks.`LyricistID` " "WHERE " "tracksMapping.`TrackID` = tracks.`ID` AND " "tracksMapping.`Priority` = (SELECT MIN(`Priority`) FROM `TracksMapping` WHERE `TrackID` = tracks.`ID`)"); auto result = prepareQuery(d->mSelectAllTracksQuery, selectAllTracksText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllTracksQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllTracksQuery.lastError(); Q_EMIT databaseError(); } } { auto selectAllTracksShortText = QStringLiteral("SELECT " "tracks.`ID`, " "tracks.`Title`, " "tracks.`ArtistName`, " "tracks.`AlbumTitle`, " "tracks.`AlbumArtistName`, " "tracks.`Duration`, " "album.`CoverFileName`, " "tracks.`TrackNumber`, " "tracks.`DiscNumber`, " "tracks.`Rating` " "FROM " "`Tracks` tracks " "LEFT JOIN " "`Albums` album " "ON " "tracks.`AlbumTitle` = album.`Title` AND " "(tracks.`AlbumArtistName` = album.`ArtistName` OR tracks.`AlbumArtistName` IS NULL ) AND " "tracks.`AlbumPath` = album.`AlbumPath` " ""); auto result = prepareQuery(d->mSelectAllTracksShortQuery, selectAllTracksShortText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllTracksShortQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllTracksShortQuery.lastError(); Q_EMIT databaseError(); } } { auto selectAllTracksFromSourceQueryText = QStringLiteral("SELECT " "tracks.`ID`, " "tracks.`Title`, " "album.`ID`, " "tracks.`ArtistName`, " "tracks.`AlbumArtistName`, " "tracksMapping.`FileName`, " "tracksMapping.`FileModifiedTime`, " "tracks.`TrackNumber`, " "tracks.`DiscNumber`, " "tracks.`Duration`, " "tracks.`AlbumTitle`, " "tracks.`Rating`, " "album.`CoverFileName`, " "(" "SELECT " "COUNT(DISTINCT tracks2.DiscNumber) <= 1 " "FROM " "`Tracks` tracks2 " "WHERE " "tracks2.`AlbumTitle` = album.`Title` AND " "(tracks2.`AlbumArtistName` = album.`ArtistName` OR " "(tracks2.`AlbumArtistName` IS NULL AND " "album.`ArtistName` IS NULL" ")" ") AND " "tracks2.`AlbumPath` = album.`AlbumPath` " ") as `IsSingleDiscAlbum`, " "trackGenre.`Name`, " "trackComposer.`Name`, " "trackLyricist.`Name`, " "tracks.`Comment`, " "tracks.`Year`, " "tracks.`Channels`, " "tracks.`BitRate`, " "tracks.`SampleRate`, " "tracks.`HasEmbeddedCover` " "FROM " "`Tracks` tracks, " "`TracksMapping` tracksMapping, " "`DiscoverSource` source " "LEFT JOIN " "`Albums` album " "ON " "tracks.`AlbumTitle` = album.`Title` AND " "(tracks.`AlbumArtistName` = album.`ArtistName` OR tracks.`AlbumArtistName` IS NULL ) AND " "tracks.`AlbumPath` = album.`AlbumPath` " "LEFT JOIN `Composer` trackComposer ON trackComposer.`ID` = tracks.`ComposerID` " "LEFT JOIN `Lyricist` trackLyricist ON trackLyricist.`ID` = tracks.`LyricistID` " "LEFT JOIN `Genre` trackGenre ON trackGenre.`ID` = tracks.`GenreID` " "WHERE " "source.`Name` = :source AND " "source.`ID` = tracksMapping.`DiscoverID` AND " "tracksMapping.`TrackID` = tracks.`ID` AND " "tracksMapping.`Priority` = (SELECT MIN(`Priority`) FROM `TracksMapping` WHERE `TrackID` = tracks.`ID`)"); auto result = prepareQuery(d->mSelectAllTracksFromSourceQuery, selectAllTracksFromSourceQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllTracksFromSourceQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllTracksFromSourceQuery.lastError(); Q_EMIT databaseError(); } } { auto selectArtistByNameText = QStringLiteral("SELECT `ID`, " "`Name` " "FROM `Artists` " "WHERE " "`Name` = :name"); auto result = prepareQuery(d->mSelectArtistByNameQuery, selectArtistByNameText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectArtistByNameQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectArtistByNameQuery.lastError(); Q_EMIT databaseError(); } } { auto selectComposerByNameText = QStringLiteral("SELECT `ID`, " "`Name` " "FROM `Composer` " "WHERE " "`Name` = :name"); auto result = prepareQuery(d->mSelectComposerByNameQuery, selectComposerByNameText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectComposerByNameQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectComposerByNameQuery.lastError(); } } { auto selectLyricistByNameText = QStringLiteral("SELECT `ID`, " "`Name` " "FROM `Lyricist` " "WHERE " "`Name` = :name"); auto result = prepareQuery(d->mSelectLyricistByNameQuery, selectLyricistByNameText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectLyricistByNameQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectLyricistByNameQuery.lastError(); } } { auto selectGenreByNameText = QStringLiteral("SELECT `ID`, " "`Name` " "FROM `Genre` " "WHERE " "`Name` = :name"); auto result = prepareQuery(d->mSelectGenreByNameQuery, selectGenreByNameText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectGenreByNameQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectGenreByNameQuery.lastError(); Q_EMIT databaseError(); } } { auto insertArtistsText = QStringLiteral("INSERT INTO `Artists` (`ID`, `Name`) " "VALUES (:artistId, :name)"); auto result = prepareQuery(d->mInsertArtistsQuery, insertArtistsText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mInsertArtistsQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mInsertArtistsQuery.lastError(); Q_EMIT databaseError(); } } { auto insertGenreText = QStringLiteral("INSERT INTO `Genre` (`ID`, `Name`) " "VALUES (:genreId, :name)"); auto result = prepareQuery(d->mInsertGenreQuery, insertGenreText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mInsertGenreQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mInsertGenreQuery.lastError(); Q_EMIT databaseError(); } } { auto insertComposerText = QStringLiteral("INSERT INTO `Composer` (`ID`, `Name`) " "VALUES (:composerId, :name)"); auto result = prepareQuery(d->mInsertComposerQuery, insertComposerText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mInsertComposerQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mInsertComposerQuery.lastError(); } } { auto insertLyricistText = QStringLiteral("INSERT INTO `Lyricist` (`ID`, `Name`) " "VALUES (:lyricistId, :name)"); auto result = prepareQuery(d->mInsertLyricistQuery, insertLyricistText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mInsertLyricistQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mInsertLyricistQuery.lastError(); } } { auto selectTrackQueryText = QStringLiteral("SELECT " "tracks.`ID`, " "tracks.`Title`, " "album.`ID`, " "tracks.`ArtistName`, " "tracks.`AlbumArtistName`, " "tracksMapping.`FileName`, " "tracksMapping.`FileModifiedTime`, " "tracks.`TrackNumber`, " "tracks.`DiscNumber`, " "tracks.`Duration`, " "tracks.`AlbumTitle`, " "tracks.`Rating`, " "album.`CoverFileName`, " "(" "SELECT " "COUNT(DISTINCT tracks2.DiscNumber) <= 1 " "FROM " "`Tracks` tracks2 " "WHERE " "tracks2.`AlbumTitle` = album.`Title` AND " "(tracks2.`AlbumArtistName` = album.`ArtistName` OR " "(tracks2.`AlbumArtistName` IS NULL AND " "album.`ArtistName` IS NULL" ")" ") AND " "tracks2.`AlbumPath` = album.`AlbumPath` " ") as `IsSingleDiscAlbum`, " "trackGenre.`Name`, " "trackComposer.`Name`, " "trackLyricist.`Name`, " "tracks.`Comment`, " "tracks.`Year`, " "tracks.`Channels`, " "tracks.`BitRate`, " "tracks.`SampleRate`, " "tracks.`HasEmbeddedCover` " "FROM " "`Tracks` tracks, " "`TracksMapping` tracksMapping " "LEFT JOIN " "`Albums` album " "ON " "tracks.`AlbumTitle` = album.`Title` AND " "(tracks.`AlbumArtistName` = album.`ArtistName` OR tracks.`AlbumArtistName` IS NULL ) AND " "tracks.`AlbumPath` = album.`AlbumPath` " "LEFT JOIN `Composer` trackComposer ON trackComposer.`ID` = tracks.`ComposerID` " "LEFT JOIN `Lyricist` trackLyricist ON trackLyricist.`ID` = tracks.`LyricistID` " "LEFT JOIN `Genre` trackGenre ON trackGenre.`ID` = tracks.`GenreID` " "WHERE " "tracksMapping.`TrackID` = tracks.`ID` AND " "album.`ID` = :albumId AND " "tracksMapping.`Priority` = (SELECT MIN(`Priority`) FROM `TracksMapping` WHERE `TrackID` = tracks.`ID`) " "ORDER BY tracks.`DiscNumber` ASC, " "tracks.`TrackNumber` ASC"); auto result = prepareQuery(d->mSelectTrackQuery, selectTrackQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackQuery.lastError(); Q_EMIT databaseError(); } } { auto selectTrackFromIdQueryText = QStringLiteral("SELECT " "tracks.`Id`, " "tracks.`Title`, " "album.`ID`, " "tracks.`ArtistName`, " "tracks.`AlbumArtistName`, " "tracksMapping.`FileName`, " "tracksMapping.`FileModifiedTime`, " "tracks.`TrackNumber`, " "tracks.`DiscNumber`, " "tracks.`Duration`, " "tracks.`AlbumTitle`, " "tracks.`Rating`, " "album.`CoverFileName`, " "(" "SELECT " "COUNT(DISTINCT tracks2.DiscNumber) <= 1 " "FROM " "`Tracks` tracks2 " "WHERE " "tracks2.`AlbumTitle` = album.`Title` AND " "(tracks2.`AlbumArtistName` = album.`ArtistName` OR " "(tracks2.`AlbumArtistName` IS NULL AND " "album.`ArtistName` IS NULL" ")" ") AND " "tracks2.`AlbumPath` = album.`AlbumPath` " ") as `IsSingleDiscAlbum`, " "trackGenre.`Name`, " "trackComposer.`Name`, " "trackLyricist.`Name`, " "tracks.`Comment`, " "tracks.`Year`, " "tracks.`Channels`, " "tracks.`BitRate`, " "tracks.`SampleRate`, " "tracks.`HasEmbeddedCover` " "FROM " "`Tracks` tracks, " "`TracksMapping` tracksMapping " "LEFT JOIN " "`Albums` album " "ON " "tracks.`AlbumTitle` = album.`Title` AND " "(tracks.`AlbumArtistName` = album.`ArtistName` OR tracks.`AlbumArtistName` IS NULL ) AND " "tracks.`AlbumPath` = album.`AlbumPath` " "LEFT JOIN `Composer` trackComposer ON trackComposer.`ID` = tracks.`ComposerID` " "LEFT JOIN `Lyricist` trackLyricist ON trackLyricist.`ID` = tracks.`LyricistID` " "LEFT JOIN `Genre` trackGenre ON trackGenre.`ID` = tracks.`GenreID` " "WHERE " "tracks.`ID` = :trackId AND " "tracksMapping.`TrackID` = tracks.`ID` AND " "tracksMapping.`Priority` = (SELECT MIN(`Priority`) FROM `TracksMapping` WHERE `TrackID` = tracks.`ID`)"); auto result = prepareQuery(d->mSelectTrackFromIdQuery, selectTrackFromIdQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackFromIdQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackFromIdQuery.lastError(); Q_EMIT databaseError(); } } { auto selectCountAlbumsQueryText = QStringLiteral("SELECT count(*) " "FROM `Albums` album " "WHERE album.`ArtistName` = :artistName "); const auto result = prepareQuery(d->mSelectCountAlbumsForArtistQuery, selectCountAlbumsQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectCountAlbumsForArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectCountAlbumsForArtistQuery.lastError(); Q_EMIT databaseError(); } } { auto selectGenreForArtistQueryText = QStringLiteral("SELECT DISTINCT trackGenre.`Name` " "FROM " "`Tracks` tracks " "LEFT JOIN " "`Albums` album " "ON " "tracks.`AlbumTitle` = album.`Title` AND " "(tracks.`AlbumArtistName` = album.`ArtistName` OR tracks.`AlbumArtistName` IS NULL ) AND " "tracks.`AlbumPath` = album.`AlbumPath` " "LEFT JOIN `Genre` trackGenre ON trackGenre.`ID` = tracks.`GenreID` " "WHERE " "album.`ArtistName` = :artistName"); const auto result = prepareQuery(d->mSelectGenreForArtistQuery, selectGenreForArtistQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectGenreForArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectGenreForArtistQuery.lastError(); Q_EMIT databaseError(); } } { auto selectGenreForAlbumQueryText = QStringLiteral("SELECT DISTINCT trackGenre.`Name` " "FROM " "`Tracks` tracks " "LEFT JOIN " "`Albums` album " "ON " "tracks.`AlbumTitle` = album.`Title` AND " "(tracks.`AlbumArtistName` = album.`ArtistName` OR tracks.`AlbumArtistName` IS NULL ) AND " "tracks.`AlbumPath` = album.`AlbumPath` " "LEFT JOIN `Genre` trackGenre ON trackGenre.`ID` = tracks.`GenreID` " "WHERE " "album.`ID` = :albumId"); const auto result = prepareQuery(d->mSelectGenreForAlbumQuery, selectGenreForAlbumQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectGenreForAlbumQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectGenreForAlbumQuery.lastError(); Q_EMIT databaseError(); } } { auto selectCountAlbumsQueryText = QStringLiteral("SELECT distinct count(album.`ID`) " "FROM " "`Tracks` tracks, " "`Albums` album " "LEFT JOIN `Composer` albumComposer ON albumComposer.`ID` = tracks.`ComposerID` " "WHERE " "(tracks.`AlbumTitle` = album.`Title` OR tracks.`AlbumTitle` IS NULL ) AND " "(tracks.`AlbumArtistName` = album.`ArtistName` OR tracks.`AlbumArtistName` IS NULL ) AND " "(tracks.`AlbumPath` = album.`AlbumPath` OR tracks.`AlbumPath` IS NULL ) AND " "albumComposer.`Name` = :artistName"); const auto result = prepareQuery(d->mSelectCountAlbumsForComposerQuery, selectCountAlbumsQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectCountAlbumsForComposerQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectCountAlbumsForComposerQuery.lastError(); Q_EMIT databaseError(); } } { auto selectCountAlbumsQueryText = QStringLiteral("SELECT distinct count(album.`ID`) " "FROM " "`Tracks` tracks, " "`Albums` album " "LEFT JOIN `Lyricist` albumLyricist ON albumLyricist.`ID` = tracks.`LyricistID` " "WHERE " "(tracks.`AlbumTitle` = album.`Title` OR tracks.`AlbumTitle` IS NULL ) AND " "(tracks.`AlbumArtistName` = album.`ArtistName` OR tracks.`AlbumArtistName` IS NULL ) AND " "(tracks.`AlbumPath` = album.`AlbumPath` OR tracks.`AlbumPath` IS NULL ) AND " "albumLyricist.`Name` = :artistName"); const auto result = prepareQuery(d->mSelectCountAlbumsForLyricistQuery, selectCountAlbumsQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectCountAlbumsForLyricistQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectCountAlbumsForLyricistQuery.lastError(); Q_EMIT databaseError(); } } { auto selectAlbumIdFromTitleQueryText = QStringLiteral("SELECT " "album.`ID` " "FROM " "`Albums` album " "WHERE " "album.`ArtistName` = :artistName AND " "album.`Title` = :title"); auto result = prepareQuery(d->mSelectAlbumIdFromTitleQuery, selectAlbumIdFromTitleQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumIdFromTitleQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumIdFromTitleQuery.lastError(); Q_EMIT databaseError(); } } { auto selectAlbumIdFromTitleAndArtistQueryText = QStringLiteral("SELECT " "album.`ID` " "FROM " "`Albums` album " "WHERE " "album.`ArtistName` = :artistName AND " "album.`Title` = :title AND " "album.`AlbumPath` = :albumPath"); auto result = prepareQuery(d->mSelectAlbumIdFromTitleAndArtistQuery, selectAlbumIdFromTitleAndArtistQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumIdFromTitleAndArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumIdFromTitleAndArtistQuery.lastError(); Q_EMIT databaseError(); } } { auto selectAlbumIdFromTitleWithoutArtistQueryText = QStringLiteral("SELECT " "album.`ID` " "FROM " "`Albums` album " "WHERE " "album.`AlbumPath` = :albumPath AND " "album.`Title` = :title AND " "album.`ArtistName` IS NULL"); auto result = prepareQuery(d->mSelectAlbumIdFromTitleWithoutArtistQuery, selectAlbumIdFromTitleWithoutArtistQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumIdFromTitleWithoutArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumIdFromTitleWithoutArtistQuery.lastError(); Q_EMIT databaseError(); } } { auto insertAlbumQueryText = QStringLiteral("INSERT INTO `Albums` " "(`ID`, " "`Title`, " "`ArtistName`, " "`AlbumPath`, " "`CoverFileName`) " "VALUES " "(:albumId, " ":title, " ":albumArtist, " ":albumPath, " ":coverFileName)"); auto result = prepareQuery(d->mInsertAlbumQuery, insertAlbumQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mInsertAlbumQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mInsertAlbumQuery.lastError(); Q_EMIT databaseError(); } } { auto insertTrackMappingQueryText = QStringLiteral("INSERT INTO " "`TracksMapping` " "(`FileName`, " "`DiscoverID`, " "`Priority`, " "`FileModifiedTime`) " "VALUES (:fileName, :discoverId, :priority, :mtime)"); auto result = prepareQuery(d->mInsertTrackMapping, insertTrackMappingQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mInsertTrackMapping.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mInsertTrackMapping.lastError(); Q_EMIT databaseError(); } } { auto initialUpdateTracksValidityQueryText = QStringLiteral("UPDATE `TracksMapping` " "SET " "`TrackID` = :trackId, " "`Priority` = :priority, " "`FileModifiedTime` = :mtime " "WHERE `FileName` = :fileName"); auto result = prepareQuery(d->mUpdateTrackMapping, initialUpdateTracksValidityQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mUpdateTrackMapping.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mUpdateTrackMapping.lastError(); Q_EMIT databaseError(); } } { auto removeTracksMappingFromSourceQueryText = QStringLiteral("DELETE FROM `TracksMapping` " "WHERE `FileName` = :fileName AND `DiscoverID` = :sourceId"); auto result = prepareQuery(d->mRemoveTracksMappingFromSource, removeTracksMappingFromSourceQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mRemoveTracksMappingFromSource.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mRemoveTracksMappingFromSource.lastError(); Q_EMIT databaseError(); } } { auto removeTracksMappingQueryText = QStringLiteral("DELETE FROM `TracksMapping` " "WHERE `FileName` = :fileName"); auto result = prepareQuery(d->mRemoveTracksMapping, removeTracksMappingQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mRemoveTracksMapping.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mRemoveTracksMapping.lastError(); Q_EMIT databaseError(); } } { auto selectTracksWithoutMappingQueryText = QStringLiteral("SELECT " "tracks.`Id`, " "tracks.`Title`, " "album.`ID`, " "tracks.`ArtistName`, " "tracks.`AlbumArtistName`, " "\"\" as FileName, " "NULL as FileModifiedTime, " "tracks.`TrackNumber`, " "tracks.`DiscNumber`, " "tracks.`Duration`, " "tracks.`AlbumTitle`, " "tracks.`Rating`, " "album.`CoverFileName`, " "(" "SELECT " "COUNT(DISTINCT tracks2.DiscNumber) <= 1 " "FROM " "`Tracks` tracks2 " "WHERE " "tracks2.`AlbumTitle` = album.`Title` AND " "(tracks2.`AlbumArtistName` = album.`ArtistName` OR " "(tracks2.`AlbumArtistName` IS NULL AND " "album.`ArtistName` IS NULL" ")" ") AND " "tracks2.`AlbumPath` = album.`AlbumPath` " ") as `IsSingleDiscAlbum`, " "trackGenre.`Name`, " "trackComposer.`Name`, " "trackLyricist.`Name`, " "tracks.`Comment`, " "tracks.`Year`, " "tracks.`Channels`, " "tracks.`BitRate`, " "tracks.`SampleRate`, " "tracks.`HasEmbeddedCover` " "FROM " "`Tracks` tracks " "LEFT JOIN " "`Albums` album " "ON " "tracks.`AlbumTitle` = album.`Title` AND " "(tracks.`AlbumArtistName` = album.`ArtistName` OR tracks.`AlbumArtistName` IS NULL ) AND " "tracks.`AlbumPath` = album.`AlbumPath` " "LEFT JOIN `Composer` trackComposer ON trackComposer.`ID` = tracks.`ComposerID` " "LEFT JOIN `Lyricist` trackLyricist ON trackLyricist.`ID` = tracks.`LyricistID` " "LEFT JOIN `Genre` trackGenre ON trackGenre.`ID` = tracks.`GenreID` " "WHERE " "tracks.`ID` NOT IN (SELECT tracksMapping2.`TrackID` FROM `TracksMapping` tracksMapping2)"); auto result = prepareQuery(d->mSelectTracksWithoutMappingQuery, selectTracksWithoutMappingQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTracksWithoutMappingQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectTracksWithoutMappingQuery.lastError(); Q_EMIT databaseError(); } } { auto selectTracksMappingQueryText = QStringLiteral("SELECT " "`TrackID`, " "`FileName`, " "`DiscoverID`, " "`Priority`, " "`FileModifiedTime` " "FROM " "`TracksMapping` " "WHERE " "`FileName` = :fileName"); auto result = prepareQuery(d->mSelectTracksMapping, selectTracksMappingQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTracksMapping.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectTracksMapping.lastError(); Q_EMIT databaseError(); } } { auto selectTracksMappingPriorityQueryText = QStringLiteral("SELECT " "`Priority` " "FROM " "`TracksMapping` " "WHERE " "`TrackID` = :trackId AND " "`FileName` = :fileName"); auto result = prepareQuery(d->mSelectTracksMappingPriority, selectTracksMappingPriorityQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTracksMappingPriority.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectTracksMappingPriority.lastError(); Q_EMIT databaseError(); } } { auto selectTracksMappingPriorityQueryByTrackIdText = QStringLiteral("SELECT " "MAX(`Priority`) " "FROM " "`TracksMapping` " "WHERE " "`TrackID` = :trackId"); auto result = prepareQuery(d->mSelectTracksMappingPriorityByTrackId, selectTracksMappingPriorityQueryByTrackIdText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTracksMappingPriorityByTrackId.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectTracksMappingPriorityByTrackId.lastError(); Q_EMIT databaseError(); } } { auto selectAllTrackFilesFromSourceQueryText = QStringLiteral("SELECT " "tracksMapping.`FileName`, " "tracksMapping.`FileModifiedTime` " "FROM " "`TracksMapping` tracksMapping " "WHERE " "tracksMapping.`DiscoverID` = :discoverId"); auto result = prepareQuery(d->mSelectAllTrackFilesFromSourceQuery, selectAllTrackFilesFromSourceQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllTrackFilesFromSourceQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllTrackFilesFromSourceQuery.lastError(); Q_EMIT databaseError(); } } { auto insertMusicSourceQueryText = QStringLiteral("INSERT OR IGNORE INTO `DiscoverSource` (`ID`, `Name`) " "VALUES (:discoverId, :name)"); auto result = prepareQuery(d->mInsertMusicSource, insertMusicSourceQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mInsertMusicSource.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mInsertMusicSource.lastError(); Q_EMIT databaseError(); } } { auto selectMusicSourceQueryText = QStringLiteral("SELECT `ID` FROM `DiscoverSource` WHERE `Name` = :name"); auto result = prepareQuery(d->mSelectMusicSource, selectMusicSourceQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectMusicSource.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectMusicSource.lastError(); Q_EMIT databaseError(); } } { auto selectTrackQueryText = QStringLiteral("SELECT " "tracks.`ID`, tracksMapping.`FileName` " "FROM " "`Tracks` tracks, " "`Albums` album, " "`TracksMapping` tracksMapping " "WHERE " "tracks.`Title` = :title AND " "album.`ID` = :album AND " "(tracks.`AlbumTitle` = album.`Title` OR tracks.`AlbumTitle` IS NULL ) AND " "(tracks.`AlbumArtistName` = album.`ArtistName` OR tracks.`AlbumArtistName` IS NULL ) AND " "(tracks.`AlbumPath` = album.`AlbumPath` OR tracks.`AlbumPath` IS NULL ) AND " "tracks.`ArtistName` = :artist AND " "tracksMapping.`TrackID` = tracks.`ID` AND " "tracksMapping.`Priority` = (SELECT MIN(`Priority`) FROM `TracksMapping` WHERE `TrackID` = tracks.`ID`)"); auto result = prepareQuery(d->mSelectTrackIdFromTitleAlbumIdArtistQuery, selectTrackQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackIdFromTitleAlbumIdArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackIdFromTitleAlbumIdArtistQuery.lastError(); Q_EMIT databaseError(); } } { auto insertTrackQueryText = QStringLiteral("INSERT INTO `Tracks` " "(" "`ID`, " "`Title`, " "`ArtistName`, " "`AlbumTitle`, " "`AlbumArtistName`, " "`AlbumPath`, " "`GenreID`, " "`ComposerID`, " "`LyricistID`, " "`Comment`, " "`TrackNumber`, " "`DiscNumber`, " "`Channels`, " "`BitRate`, " "`SampleRate`, " "`Year`, " "`Duration`, " "`Rating`, " "`HasEmbeddedCover`) " "VALUES " "(" ":trackId, " ":title, " ":artistName, " ":albumTitle, " ":albumArtistName, " ":albumPath, " ":genreId, " ":composerId, " ":lyricistId, " ":comment, " ":trackNumber, " ":discNumber, " ":channels, " ":bitRate, " ":sampleRate, " ":year, " ":trackDuration, " ":trackRating, " ":hasEmbeddedCover)"); auto result = prepareQuery(d->mInsertTrackQuery, insertTrackQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mInsertTrackQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mInsertTrackQuery.lastError(); Q_EMIT databaseError(); } } { auto updateTrackQueryText = QStringLiteral("UPDATE `Tracks` " "SET " "`Title` = :title, " "`ArtistName` = :artistName, " "`AlbumTitle` = :albumTitle, " "`AlbumArtistName` = :albumArtistName, " "`AlbumPath` = :albumPath, " "`GenreID` = :genreId, " "`ComposerID` = :composerId, " "`LyricistID` = :lyricistId, " "`Comment` = :comment, " "`TrackNumber` = :trackNumber, " "`DiscNumber` = :discNumber, " "`Channels` = :channels, " "`BitRate` = :bitRate, " "`SampleRate` = :sampleRate, " "`Year` = :year, " " `Duration` = :trackDuration, " "`Rating` = :trackRating " "WHERE " "`ID` = :trackId"); auto result = prepareQuery(d->mUpdateTrackQuery, updateTrackQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mUpdateTrackQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mUpdateTrackQuery.lastError(); Q_EMIT databaseError(); } } { auto updateAlbumArtistQueryText = QStringLiteral("UPDATE `Albums` " "SET " "`ArtistName` = :artistName " "WHERE " "`ID` = :albumId"); auto result = prepareQuery(d->mUpdateAlbumArtistQuery, updateAlbumArtistQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mUpdateAlbumArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mUpdateAlbumArtistQuery.lastError(); Q_EMIT databaseError(); } } { auto updateAlbumArtistInTracksQueryText = QStringLiteral("UPDATE `Tracks` " "SET " "`AlbumArtistName` = :artistName " "WHERE " "`AlbumTitle` = :albumTitle AND " "`AlbumPath` = :albumPath AND " "`AlbumArtistName` IS NULL"); auto result = prepareQuery(d->mUpdateAlbumArtistInTracksQuery, updateAlbumArtistInTracksQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mUpdateAlbumArtistInTracksQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mUpdateAlbumArtistInTracksQuery.lastError(); Q_EMIT databaseError(); } } { auto queryMaximumTrackIdQueryText = QStringLiteral("SELECT MAX(tracks.`ID`)" "FROM " "`Tracks` tracks"); auto result = prepareQuery(d->mQueryMaximumTrackIdQuery, queryMaximumTrackIdQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mQueryMaximumTrackIdQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mQueryMaximumTrackIdQuery.lastError(); Q_EMIT databaseError(); } } { auto queryMaximumAlbumIdQueryText = QStringLiteral("SELECT MAX(albums.`ID`)" "FROM " "`Albums` albums"); auto result = prepareQuery(d->mQueryMaximumAlbumIdQuery, queryMaximumAlbumIdQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mQueryMaximumAlbumIdQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mQueryMaximumAlbumIdQuery.lastError(); Q_EMIT databaseError(); } } { auto queryMaximumArtistIdQueryText = QStringLiteral("SELECT MAX(artists.`ID`)" "FROM " "`Artists` artists"); auto result = prepareQuery(d->mQueryMaximumArtistIdQuery, queryMaximumArtistIdQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mQueryMaximumArtistIdQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mQueryMaximumArtistIdQuery.lastError(); Q_EMIT databaseError(); } } { auto queryMaximumLyricistIdQueryText = QStringLiteral("SELECT MAX(lyricists.`ID`)" "FROM " "`Lyricist` lyricists"); auto result = prepareQuery(d->mQueryMaximumLyricistIdQuery, queryMaximumLyricistIdQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mQueryMaximumLyricistIdQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mQueryMaximumLyricistIdQuery.lastError(); Q_EMIT databaseError(); } } { auto queryMaximumComposerIdQueryText = QStringLiteral("SELECT MAX(composers.`ID`)" "FROM " "`Composer` composers"); auto result = prepareQuery(d->mQueryMaximumComposerIdQuery, queryMaximumComposerIdQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mQueryMaximumComposerIdQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mQueryMaximumComposerIdQuery.lastError(); Q_EMIT databaseError(); } } { auto queryMaximumGenreIdQueryText = QStringLiteral("SELECT MAX(genres.`ID`)" "FROM " "`Genre` genres"); auto result = prepareQuery(d->mQueryMaximumGenreIdQuery, queryMaximumGenreIdQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mQueryMaximumGenreIdQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mQueryMaximumGenreIdQuery.lastError(); Q_EMIT databaseError(); } } { auto selectTrackQueryText = QStringLiteral("SELECT " "tracks.ID " "FROM " "`Tracks` tracks " "WHERE " "tracks.`Title` = :title AND " "tracks.`AlbumTitle` = :album AND " "tracks.`TrackNumber` = :trackNumber AND " "tracks.`DiscNumber` = :discNumber AND " "tracks.`ArtistName` = :artist"); auto result = prepareQuery(d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery, selectTrackQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.lastError(); Q_EMIT databaseError(); } } { auto selectTrackQueryText = QStringLiteral("SELECT " "tracks.ID " "FROM " "`Tracks` tracks, " "`Albums` albums " "WHERE " "tracks.`Title` = :title AND " "tracks.`ArtistName` = :trackArtist AND " "tracks.`AlbumTitle` = :album AND " "(tracks.`AlbumArtistName` = :albumArtist OR tracks.`AlbumArtistName` IS NULL) AND " "tracks.`AlbumPath` = :albumPath AND " "tracks.`TrackNumber` = :trackNumber"); auto result = prepareQuery(d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery, selectTrackQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.lastError(); Q_EMIT databaseError(); } } { auto selectAlbumArtUriFromAlbumIdQueryText = QStringLiteral("SELECT `CoverFileName`" "FROM " "`Albums` " "WHERE " "`ID` = :albumId"); auto result = prepareQuery(d->mSelectAlbumArtUriFromAlbumIdQuery, selectAlbumArtUriFromAlbumIdQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumArtUriFromAlbumIdQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumArtUriFromAlbumIdQuery.lastError(); Q_EMIT databaseError(); } } { auto updateAlbumArtUriFromAlbumIdQueryText = QStringLiteral("UPDATE `Albums` " "SET `CoverFileName` = :coverFileName " "WHERE " "`ID` = :albumId"); auto result = prepareQuery(d->mUpdateAlbumArtUriFromAlbumIdQuery, updateAlbumArtUriFromAlbumIdQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mUpdateAlbumArtUriFromAlbumIdQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mUpdateAlbumArtUriFromAlbumIdQuery.lastError(); Q_EMIT databaseError(); } } { auto selectTracksFromArtistQueryText = QStringLiteral("SELECT " "tracks.`ID`, " "tracks.`Title`, " "album.`ID`, " "tracks.`ArtistName`, " "tracks.`AlbumArtistName`, " "tracksMapping.`FileName`, " "tracksMapping.`FileModifiedTime`, " "tracks.`TrackNumber`, " "tracks.`DiscNumber`, " "tracks.`Duration`, " "tracks.`AlbumTitle`, " "tracks.`Rating`, " "album.`CoverFileName`, " "(" "SELECT " "COUNT(DISTINCT tracks2.DiscNumber) <= 1 " "FROM " "`Tracks` tracks2 " "WHERE " "tracks2.`AlbumTitle` = album.`Title` AND " "(tracks2.`AlbumArtistName` = album.`ArtistName` OR " "(tracks2.`AlbumArtistName` IS NULL AND " "album.`ArtistName` IS NULL" ")" ") AND " "tracks2.`AlbumPath` = album.`AlbumPath` " ") as `IsSingleDiscAlbum`, " "trackGenre.`Name`, " "trackComposer.`Name`, " "trackLyricist.`Name`, " "tracks.`Comment`, " "tracks.`Year`, " "tracks.`Channels`, " "tracks.`BitRate`, " "tracks.`SampleRate`, " "tracks.`HasEmbeddedCover` " "FROM " "`Tracks` tracks, " "`TracksMapping` tracksMapping " "LEFT JOIN " "`Albums` album " "ON " "tracks.`AlbumTitle` = album.`Title` AND " "(tracks.`AlbumArtistName` = album.`ArtistName` OR tracks.`AlbumArtistName` IS NULL ) AND " "tracks.`AlbumPath` = album.`AlbumPath` " "LEFT JOIN `Composer` trackComposer ON trackComposer.`ID` = tracks.`ComposerID` " "LEFT JOIN `Lyricist` trackLyricist ON trackLyricist.`ID` = tracks.`LyricistID` " "LEFT JOIN `Genre` trackGenre ON trackGenre.`ID` = tracks.`GenreID` " "WHERE " "tracks.`ArtistName` = :artistName AND " "tracksMapping.`TrackID` = tracks.`ID` AND " "tracksMapping.`Priority` = (SELECT MIN(`Priority`) FROM `TracksMapping` WHERE `TrackID` = tracks.`ID`) " "ORDER BY tracks.`Title` ASC, " "album.`Title` ASC"); auto result = prepareQuery(d->mSelectTracksFromArtist, selectTracksFromArtistQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTracksFromArtist.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectTracksFromArtist.lastError(); Q_EMIT databaseError(); } } { auto selectAlbumIdsFromArtistQueryText = QStringLiteral("SELECT " "album.`ID` " "FROM " "`Albums` album " "WHERE " "album.`ArtistName` = :artistName"); auto result = prepareQuery(d->mSelectAlbumIdsFromArtist, selectAlbumIdsFromArtistQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumIdsFromArtist.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectAlbumIdsFromArtist.lastError(); Q_EMIT databaseError(); } } { auto selectArtistQueryText = QStringLiteral("SELECT `ID`, " "`Name` " "FROM `Artists` " "WHERE " "`ID` = :artistId"); auto result = prepareQuery(d->mSelectArtistQuery, selectArtistQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectArtistQuery.lastError(); Q_EMIT databaseError(); } } { auto selectGenreQueryText = QStringLiteral("SELECT `ID`, " "`Name` " "FROM `Genre` " "WHERE " "`ID` = :genreId"); auto result = prepareQuery(d->mSelectGenreQuery, selectGenreQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectGenreQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectGenreQuery.lastError(); Q_EMIT databaseError(); } } { auto selectComposerQueryText = QStringLiteral("SELECT `ID`, " "`Name` " "FROM `Composer` " "WHERE " "`ID` = :composerId"); auto result = prepareQuery(d->mSelectComposerQuery, selectComposerQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectComposerQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectComposerQuery.lastError(); } } { auto selectLyricistQueryText = QStringLiteral("SELECT `ID`, " "`Name` " "FROM `Lyricist` " "WHERE " "`ID` = :lyricistId"); auto result = prepareQuery(d->mSelectLyricistQuery, selectLyricistQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectLyricistQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectLyricistQuery.lastError(); } } { auto selectTrackFromFilePathQueryText = QStringLiteral("SELECT " "tracks.`ID`, " "tracks.`Title`, " "album.`ID`, " "tracks.`ArtistName`, " "tracks.`AlbumArtistName`, " "tracksMapping.`FileName`, " "tracksMapping.`FileModifiedTime`, " "tracks.`TrackNumber`, " "tracks.`DiscNumber`, " "tracks.`Duration`, " "tracks.`AlbumTitle`, " "tracks.`Rating`, " "album.`CoverFileName`, " "(" "SELECT " "COUNT(DISTINCT tracks2.DiscNumber) <= 1 " "FROM " "`Tracks` tracks2 " "WHERE " "tracks2.`AlbumTitle` = album.`Title` AND " "(tracks2.`AlbumArtistName` = album.`ArtistName` OR " "(tracks2.`AlbumArtistName` IS NULL AND " "album.`ArtistName` IS NULL" ")" ") AND " "tracks2.`AlbumPath` = album.`AlbumPath` " ") as `IsSingleDiscAlbum`, " "trackGenre.`Name`, " "trackComposer.`Name`, " "trackLyricist.`Name`, " "tracks.`Comment`, " "tracks.`Year`, " "tracks.`Channels`, " "tracks.`BitRate`, " "tracks.`SampleRate` " "FROM " "`Tracks` tracks, " "`TracksMapping` tracksMapping " "LEFT JOIN " "`Albums` album " "ON " "tracks.`AlbumTitle` = album.`Title` AND " "(tracks.`AlbumArtistName` = album.`ArtistName` OR tracks.`AlbumArtistName` IS NULL ) AND " "tracks.`AlbumPath` = album.`AlbumPath` " "LEFT JOIN `Composer` trackComposer ON trackComposer.`ID` = tracks.`ComposerID` " "LEFT JOIN `Lyricist` trackLyricist ON trackLyricist.`ID` = tracks.`LyricistID` " "LEFT JOIN `Genre` trackGenre ON trackGenre.`ID` = tracks.`GenreID` " "WHERE " "tracksMapping.`TrackID` = tracks.`ID` AND " "tracksMapping.`FileName` = :filePath AND " "tracksMapping.`Priority` = (SELECT MIN(`Priority`) FROM `TracksMapping` WHERE `TrackID` = tracks.`ID`)"); auto result = prepareQuery(d->mSelectTrackFromFilePathQuery, selectTrackFromFilePathQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackFromFilePathQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mSelectTrackFromFilePathQuery.lastError(); Q_EMIT databaseError(); } } { auto removeTrackQueryText = QStringLiteral("DELETE FROM `Tracks` " "WHERE " "`ID` = :trackId"); auto result = prepareQuery(d->mRemoveTrackQuery, removeTrackQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mRemoveTrackQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mRemoveTrackQuery.lastError(); Q_EMIT databaseError(); } } { auto removeAlbumQueryText = QStringLiteral("DELETE FROM `Albums` " "WHERE " "`ID` = :albumId"); auto result = prepareQuery(d->mRemoveAlbumQuery, removeAlbumQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mRemoveAlbumQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mRemoveAlbumQuery.lastError(); Q_EMIT databaseError(); } } { auto removeAlbumQueryText = QStringLiteral("DELETE FROM `Artists` " "WHERE " "`ID` = :artistId"); auto result = prepareQuery(d->mRemoveArtistQuery, removeAlbumQueryText); if (!result) { qDebug() << "DatabaseInterface::initRequest" << d->mRemoveArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::initRequest" << d->mRemoveArtistQuery.lastError(); Q_EMIT databaseError(); } } finishTransaction(); d->mInitFinished = true; Q_EMIT requestsInitDone(); } qulonglong DatabaseInterface::insertAlbum(const QString &title, const QString &albumArtist, const QString &trackArtist, const QString &trackPath, const QUrl &albumArtURI) { auto result = qulonglong(0); if (title.isEmpty()) { return result; } if (!albumArtist.isEmpty() || !trackArtist.isEmpty()) { d->mSelectAlbumIdFromTitleAndArtistQuery.bindValue(QStringLiteral(":title"), title); d->mSelectAlbumIdFromTitleAndArtistQuery.bindValue(QStringLiteral(":albumPath"), trackPath); if (!albumArtist.isEmpty()) { d->mSelectAlbumIdFromTitleAndArtistQuery.bindValue(QStringLiteral(":artistName"), albumArtist); } else { d->mSelectAlbumIdFromTitleAndArtistQuery.bindValue(QStringLiteral(":artistName"), trackArtist); } auto queryResult = d->mSelectAlbumIdFromTitleAndArtistQuery.exec(); if (!queryResult || !d->mSelectAlbumIdFromTitleAndArtistQuery.isSelect() || !d->mSelectAlbumIdFromTitleAndArtistQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertAlbum" << d->mSelectAlbumIdFromTitleAndArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::insertAlbum" << d->mSelectAlbumIdFromTitleAndArtistQuery.boundValues(); qDebug() << "DatabaseInterface::insertAlbum" << d->mSelectAlbumIdFromTitleAndArtistQuery.lastError(); d->mSelectAlbumIdFromTitleAndArtistQuery.finish(); return result; } if (d->mSelectAlbumIdFromTitleAndArtistQuery.next()) { result = d->mSelectAlbumIdFromTitleAndArtistQuery.record().value(0).toULongLong(); d->mSelectAlbumIdFromTitleAndArtistQuery.finish(); return result; } d->mSelectAlbumIdFromTitleAndArtistQuery.finish(); } if (result == 0) { d->mSelectAlbumIdFromTitleWithoutArtistQuery.bindValue(QStringLiteral(":title"), title); d->mSelectAlbumIdFromTitleWithoutArtistQuery.bindValue(QStringLiteral(":albumPath"), trackPath); auto queryResult = d->mSelectAlbumIdFromTitleWithoutArtistQuery.exec(); if (!queryResult || !d->mSelectAlbumIdFromTitleWithoutArtistQuery.isSelect() || !d->mSelectAlbumIdFromTitleWithoutArtistQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertAlbum" << d->mSelectAlbumIdFromTitleWithoutArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::insertAlbum" << d->mSelectAlbumIdFromTitleWithoutArtistQuery.boundValues(); qDebug() << "DatabaseInterface::insertAlbum" << d->mSelectAlbumIdFromTitleWithoutArtistQuery.lastError(); d->mSelectAlbumIdFromTitleWithoutArtistQuery.finish(); return result; } if (d->mSelectAlbumIdFromTitleWithoutArtistQuery.next()) { result = d->mSelectAlbumIdFromTitleWithoutArtistQuery.record().value(0).toULongLong(); d->mSelectAlbumIdFromTitleWithoutArtistQuery.finish(); return result; } d->mSelectAlbumIdFromTitleWithoutArtistQuery.finish(); } d->mInsertAlbumQuery.bindValue(QStringLiteral(":albumId"), d->mAlbumId); d->mInsertAlbumQuery.bindValue(QStringLiteral(":title"), title); if (!albumArtist.isEmpty()) { insertArtist(albumArtist); d->mInsertAlbumQuery.bindValue(QStringLiteral(":albumArtist"), albumArtist); } else { d->mInsertAlbumQuery.bindValue(QStringLiteral(":albumArtist"), {}); } d->mInsertAlbumQuery.bindValue(QStringLiteral(":albumPath"), trackPath); d->mInsertAlbumQuery.bindValue(QStringLiteral(":coverFileName"), albumArtURI); auto queryResult = d->mInsertAlbumQuery.exec(); if (!queryResult || !d->mInsertAlbumQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertAlbum" << d->mInsertAlbumQuery.lastQuery(); qDebug() << "DatabaseInterface::insertAlbum" << d->mInsertAlbumQuery.boundValues(); qDebug() << "DatabaseInterface::insertAlbum" << d->mInsertAlbumQuery.lastError(); d->mInsertAlbumQuery.finish(); return result; } result = d->mAlbumId; d->mInsertAlbumQuery.finish(); ++d->mAlbumId; d->mInsertedAlbums.insert(result); return result; } bool DatabaseInterface::updateAlbumFromId(qulonglong albumId, const QUrl &albumArtUri, const MusicAudioTrack ¤tTrack, const QString &albumPath) { auto modifiedAlbum = false; modifiedAlbum = true; if (!albumArtUri.isValid()) { return modifiedAlbum; } auto storedAlbumArtUri = internalAlbumArtUriFromAlbumId(albumId); if (!storedAlbumArtUri.isValid() || storedAlbumArtUri != albumArtUri) { d->mUpdateAlbumArtUriFromAlbumIdQuery.bindValue(QStringLiteral(":albumId"), albumId); d->mUpdateAlbumArtUriFromAlbumIdQuery.bindValue(QStringLiteral(":coverFileName"), albumArtUri); auto result = d->mUpdateAlbumArtUriFromAlbumIdQuery.exec(); if (!result || !d->mUpdateAlbumArtUriFromAlbumIdQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::updateAlbumFromId" << d->mUpdateAlbumArtUriFromAlbumIdQuery.lastQuery(); qDebug() << "DatabaseInterface::updateAlbumFromId" << d->mUpdateAlbumArtUriFromAlbumIdQuery.boundValues(); qDebug() << "DatabaseInterface::updateAlbumFromId" << d->mUpdateAlbumArtUriFromAlbumIdQuery.lastError(); d->mUpdateAlbumArtUriFromAlbumIdQuery.finish(); return modifiedAlbum; } d->mUpdateAlbumArtUriFromAlbumIdQuery.finish(); modifiedAlbum = true; } if (!isValidArtist(albumId) && currentTrack.isValidAlbumArtist()) { updateAlbumArtist(albumId, currentTrack.albumName(), albumPath, currentTrack.albumArtist()); modifiedAlbum = true; } return modifiedAlbum; } qulonglong DatabaseInterface::insertArtist(const QString &name) { auto result = qulonglong(0); if (name.isEmpty()) { return result; } d->mSelectArtistByNameQuery.bindValue(QStringLiteral(":name"), name); auto queryResult = d->mSelectArtistByNameQuery.exec(); if (!queryResult || !d->mSelectArtistByNameQuery.isSelect() || !d->mSelectArtistByNameQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertArtist" << d->mSelectArtistByNameQuery.lastQuery(); qDebug() << "DatabaseInterface::insertArtist" << d->mSelectArtistByNameQuery.boundValues(); qDebug() << "DatabaseInterface::insertArtist" << d->mSelectArtistByNameQuery.lastError(); d->mSelectArtistByNameQuery.finish(); return result; } if (d->mSelectArtistByNameQuery.next()) { result = d->mSelectArtistByNameQuery.record().value(0).toULongLong(); d->mSelectArtistByNameQuery.finish(); return result; } d->mSelectArtistByNameQuery.finish(); d->mInsertArtistsQuery.bindValue(QStringLiteral(":artistId"), d->mArtistId); d->mInsertArtistsQuery.bindValue(QStringLiteral(":name"), name); queryResult = d->mInsertArtistsQuery.exec(); if (!queryResult || !d->mInsertArtistsQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertArtist" << d->mInsertArtistsQuery.lastQuery(); qDebug() << "DatabaseInterface::insertArtist" << d->mInsertArtistsQuery.boundValues(); qDebug() << "DatabaseInterface::insertArtist" << d->mInsertArtistsQuery.lastError(); d->mInsertArtistsQuery.finish(); return result; } result = d->mArtistId; ++d->mArtistId; d->mInsertedArtists.insert(result); d->mInsertArtistsQuery.finish(); return result; } qulonglong DatabaseInterface::insertComposer(const QString &name) { auto result = qulonglong(0); if (name.isEmpty()) { return result; } d->mSelectComposerByNameQuery.bindValue(QStringLiteral(":name"), name); auto queryResult = d->mSelectComposerByNameQuery.exec(); if (!queryResult || !d->mSelectComposerByNameQuery.isSelect() || !d->mSelectComposerByNameQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertComposer" << d->mSelectComposerByNameQuery.lastQuery(); qDebug() << "DatabaseInterface::insertComposer" << d->mSelectComposerByNameQuery.boundValues(); qDebug() << "DatabaseInterface::insertComposer" << d->mSelectComposerByNameQuery.lastError(); d->mSelectComposerByNameQuery.finish(); return result; } if (d->mSelectComposerByNameQuery.next()) { result = d->mSelectComposerByNameQuery.record().value(0).toULongLong(); d->mSelectComposerByNameQuery.finish(); return result; } d->mSelectComposerByNameQuery.finish(); d->mInsertComposerQuery.bindValue(QStringLiteral(":composerId"), d->mComposerId); d->mInsertComposerQuery.bindValue(QStringLiteral(":name"), name); queryResult = d->mInsertComposerQuery.exec(); if (!queryResult || !d->mInsertComposerQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertComposer" << d->mInsertComposerQuery.lastQuery(); qDebug() << "DatabaseInterface::insertComposer" << d->mInsertComposerQuery.boundValues(); qDebug() << "DatabaseInterface::insertComposer" << d->mInsertComposerQuery.lastError(); d->mInsertComposerQuery.finish(); return result; } result = d->mComposerId; ++d->mComposerId; d->mInsertComposerQuery.finish(); Q_EMIT composersAdded(internalAllComposersPartialData()); return result; } qulonglong DatabaseInterface::insertGenre(const QString &name) { auto result = qulonglong(0); if (name.isEmpty()) { return result; } d->mSelectGenreByNameQuery.bindValue(QStringLiteral(":name"), name); auto queryResult = d->mSelectGenreByNameQuery.exec(); if (!queryResult || !d->mSelectGenreByNameQuery.isSelect() || !d->mSelectGenreByNameQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertGenre" << d->mSelectGenreByNameQuery.lastQuery(); qDebug() << "DatabaseInterface::insertGenre" << d->mSelectGenreByNameQuery.boundValues(); qDebug() << "DatabaseInterface::insertGenre" << d->mSelectGenreByNameQuery.lastError(); d->mSelectGenreByNameQuery.finish(); return result; } if (d->mSelectGenreByNameQuery.next()) { result = d->mSelectGenreByNameQuery.record().value(0).toULongLong(); d->mSelectGenreByNameQuery.finish(); return result; } d->mSelectGenreByNameQuery.finish(); d->mInsertGenreQuery.bindValue(QStringLiteral(":genreId"), d->mGenreId); d->mInsertGenreQuery.bindValue(QStringLiteral(":name"), name); queryResult = d->mInsertGenreQuery.exec(); if (!queryResult || !d->mInsertGenreQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertGenre" << d->mInsertGenreQuery.lastQuery(); qDebug() << "DatabaseInterface::insertGenre" << d->mInsertGenreQuery.boundValues(); qDebug() << "DatabaseInterface::insertGenre" << d->mInsertGenreQuery.lastError(); d->mInsertGenreQuery.finish(); return result; } result = d->mGenreId; ++d->mGenreId; d->mInsertGenreQuery.finish(); Q_EMIT genresAdded({{{DatabaseIdRole, result}}}); return result; } void DatabaseInterface::insertTrackOrigin(const QUrl &fileNameURI, const QDateTime &fileModifiedTime, qulonglong discoverId) { d->mInsertTrackMapping.bindValue(QStringLiteral(":discoverId"), discoverId); d->mInsertTrackMapping.bindValue(QStringLiteral(":fileName"), fileNameURI); d->mInsertTrackMapping.bindValue(QStringLiteral(":priority"), 1); d->mInsertTrackMapping.bindValue(QStringLiteral(":mtime"), fileModifiedTime); auto queryResult = d->mInsertTrackMapping.exec(); if (!queryResult || !d->mInsertTrackMapping.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertArtist" << d->mInsertTrackMapping.lastQuery(); qDebug() << "DatabaseInterface::insertArtist" << d->mInsertTrackMapping.boundValues(); qDebug() << "DatabaseInterface::insertArtist" << d->mInsertTrackMapping.lastError(); d->mInsertTrackMapping.finish(); return; } d->mInsertTrackMapping.finish(); } void DatabaseInterface::updateTrackOrigin(qulonglong trackId, const QUrl &fileName, const QDateTime &fileModifiedTime) { d->mUpdateTrackMapping.bindValue(QStringLiteral(":trackId"), trackId); d->mUpdateTrackMapping.bindValue(QStringLiteral(":fileName"), fileName); d->mUpdateTrackMapping.bindValue(QStringLiteral(":priority"), computeTrackPriority(trackId, fileName)); d->mUpdateTrackMapping.bindValue(QStringLiteral(":mtime"), fileModifiedTime); auto queryResult = d->mUpdateTrackMapping.exec(); if (!queryResult || !d->mUpdateTrackMapping.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::updateTrackOrigin" << d->mUpdateTrackMapping.lastQuery(); qDebug() << "DatabaseInterface::updateTrackOrigin" << d->mUpdateTrackMapping.boundValues(); qDebug() << "DatabaseInterface::updateTrackOrigin" << d->mUpdateTrackMapping.lastError(); d->mUpdateTrackMapping.finish(); return; } d->mInsertTrackMapping.finish(); } int DatabaseInterface::computeTrackPriority(qulonglong trackId, const QUrl &fileName) { auto result = int(1); if (!d) { return result; } d->mSelectTracksMappingPriority.bindValue(QStringLiteral(":trackId"), trackId); d->mSelectTracksMappingPriority.bindValue(QStringLiteral(":fileName"), fileName); auto queryResult = d->mSelectTracksMappingPriority.exec(); if (!queryResult || !d->mSelectTracksMappingPriority.isSelect() || !d->mSelectTracksMappingPriority.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::internalTrackIdFromFileName" << d->mSelectTracksMappingPriority.lastQuery(); qDebug() << "DatabaseInterface::internalTrackIdFromFileName" << d->mSelectTracksMappingPriority.boundValues(); qDebug() << "DatabaseInterface::internalTrackIdFromFileName" << d->mSelectTracksMappingPriority.lastError(); d->mSelectTracksMappingPriority.finish(); return result; } if (d->mSelectTracksMappingPriority.next()) { result = d->mSelectTracksMappingPriority.record().value(0).toInt(); d->mSelectTracksMappingPriority.finish(); return result; } d->mSelectTracksMappingPriority.finish(); d->mSelectTracksMappingPriorityByTrackId.bindValue(QStringLiteral(":trackId"), trackId); queryResult = d->mSelectTracksMappingPriorityByTrackId.exec(); if (!queryResult || !d->mSelectTracksMappingPriorityByTrackId.isSelect() || !d->mSelectTracksMappingPriorityByTrackId.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::internalTrackIdFromFileName" << d->mSelectTracksMappingPriorityByTrackId.lastQuery(); qDebug() << "DatabaseInterface::internalTrackIdFromFileName" << d->mSelectTracksMappingPriorityByTrackId.boundValues(); qDebug() << "DatabaseInterface::internalTrackIdFromFileName" << d->mSelectTracksMappingPriorityByTrackId.lastError(); d->mSelectTracksMappingPriorityByTrackId.finish(); return result; } if (d->mSelectTracksMappingPriorityByTrackId.next()) { result = d->mSelectTracksMappingPriorityByTrackId.record().value(0).toInt() + 1; } d->mSelectTracksMappingPriorityByTrackId.finish(); return result; } qulonglong DatabaseInterface::internalInsertTrack(const MusicAudioTrack &oneTrack, const QHash &covers, qulonglong originTrackId, TrackFileInsertType insertType) { qulonglong resultId = 0; if (oneTrack.title().isEmpty()) { return resultId; } auto oldTrack = oneTrack; QUrl::FormattingOptions currentOptions = QUrl::PreferLocalFile | QUrl::RemoveAuthority | QUrl::RemoveFilename | QUrl::RemoveFragment | QUrl::RemovePassword | QUrl::RemovePort | QUrl::RemoveQuery | QUrl::RemoveScheme | QUrl::RemoveUserInfo; const auto &trackPath = oneTrack.resourceURI().toString(currentOptions); auto albumId = insertAlbum(oneTrack.albumName(), (oneTrack.isValidAlbumArtist() ? oneTrack.albumArtist() : QString()), oneTrack.artist(), trackPath, covers[oneTrack.resourceURI().toString()]); auto otherTrackId = getDuplicateTrackIdFromTitleAlbumTrackDiscNumber(oneTrack.title(), oneTrack.artist(), oneTrack.albumName(), oneTrack.albumArtist(), trackPath, oneTrack.trackNumber(), oneTrack.discNumber()); bool isModifiedTrack = (otherTrackId != 0) || (insertType == TrackFileInsertType::ModifiedTrackFileInsert); bool isSameTrack = false; qulonglong oldAlbumId = 0; if (isModifiedTrack) { if (otherTrackId == 0) { otherTrackId = internalTrackIdFromFileName(oneTrack.resourceURI()); } originTrackId = otherTrackId; oldTrack = internalTrackFromDatabaseId(originTrackId); isSameTrack = (oldTrack.title() == oneTrack.title()); isSameTrack = isSameTrack && (oldTrack.albumName() == oneTrack.albumName()); isSameTrack = isSameTrack && (oldTrack.artist() == oneTrack.artist()); isSameTrack = isSameTrack && (oldTrack.albumArtist() == oneTrack.albumArtist()); isSameTrack = isSameTrack && (oldTrack.trackNumber() == oneTrack.trackNumber()); isSameTrack = isSameTrack && (oldTrack.discNumber() == oneTrack.discNumber()); isSameTrack = isSameTrack && (oldTrack.duration() == oneTrack.duration()); isSameTrack = isSameTrack && (oldTrack.rating() == oneTrack.rating()); isSameTrack = isSameTrack && (oldTrack.resourceURI() == oneTrack.resourceURI()); isSameTrack = isSameTrack && (oldTrack.genre() == oneTrack.genre()); isSameTrack = isSameTrack && (oldTrack.composer() == oneTrack.composer()); isSameTrack = isSameTrack && (oldTrack.lyricist() == oneTrack.lyricist()); isSameTrack = isSameTrack && (oldTrack.comment() == oneTrack.comment()); isSameTrack = isSameTrack && (oldTrack.year() == oneTrack.year()); isSameTrack = isSameTrack && (oldTrack.channels() == oneTrack.channels()); isSameTrack = isSameTrack && (oldTrack.bitRate() == oneTrack.bitRate()); isSameTrack = isSameTrack && (oldTrack.sampleRate() == oneTrack.sampleRate()); oldAlbumId = internalAlbumIdFromTitleAndArtist(oldTrack.albumName(), oldTrack.albumArtist()); if (!isSameTrack) { auto newTrack = oneTrack; newTrack.setDatabaseId(oldTrack.databaseId()); updateTrackInDatabase(newTrack, trackPath); updateTrackOrigin(newTrack.databaseId(), oneTrack.resourceURI(), oneTrack.fileModificationTime()); updateAlbumFromId(albumId, oneTrack.albumCover(), oneTrack, trackPath); recordModifiedTrack(originTrackId); if (albumId != 0) { recordModifiedAlbum(albumId); } if (oldAlbumId != 0) { auto tracksCount = fetchTrackIds(oldAlbumId).count(); if (tracksCount) { recordModifiedAlbum(oldAlbumId); } else { removeAlbumInDatabase(oldAlbumId); Q_EMIT albumRemoved(oldAlbumId); } } isSameTrack = true; } } else { originTrackId = d->mTrackId; } resultId = originTrackId; const auto &albumData = internalOneAlbumPartialData(albumId); if (!isSameTrack) { d->mInsertTrackQuery.bindValue(QStringLiteral(":trackId"), originTrackId); d->mInsertTrackQuery.bindValue(QStringLiteral(":title"), oneTrack.title()); insertArtist(oneTrack.artist()); d->mInsertTrackQuery.bindValue(QStringLiteral(":artistName"), oneTrack.artist()); d->mInsertTrackQuery.bindValue(QStringLiteral(":albumTitle"), albumData[AlbumDataType::key_type::TitleRole]); d->mInsertTrackQuery.bindValue(QStringLiteral(":albumArtistName"), albumData[AlbumDataType::key_type::ArtistRole]); d->mInsertTrackQuery.bindValue(QStringLiteral(":albumPath"), trackPath); 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 genreId = insertGenre(oneTrack.genre()); if (genreId != 0) { d->mInsertTrackQuery.bindValue(QStringLiteral(":genreId"), genreId); } else { d->mInsertTrackQuery.bindValue(QStringLiteral(":genreId"), {}); } auto composerId = insertComposer(oneTrack.composer()); if (composerId != 0) { d->mInsertTrackQuery.bindValue(QStringLiteral(":composerId"), composerId); } else { d->mInsertTrackQuery.bindValue(QStringLiteral(":composerId"), {}); } auto lyricistId = insertLyricist(oneTrack.lyricist()); if (lyricistId != 0) { d->mInsertTrackQuery.bindValue(QStringLiteral(":lyricistId"), lyricistId); } else { d->mInsertTrackQuery.bindValue(QStringLiteral(":lyricistId"), {}); } d->mInsertTrackQuery.bindValue(QStringLiteral(":comment"), oneTrack.comment()); d->mInsertTrackQuery.bindValue(QStringLiteral(":year"), oneTrack.year()); d->mInsertTrackQuery.bindValue(QStringLiteral(":channels"), oneTrack.channels()); d->mInsertTrackQuery.bindValue(QStringLiteral(":bitRate"), oneTrack.bitRate()); d->mInsertTrackQuery.bindValue(QStringLiteral(":sampleRate"), oneTrack.sampleRate()); d->mInsertTrackQuery.bindValue(QStringLiteral(":hasEmbeddedCover"), oneTrack.hasEmbeddedCover()); auto result = d->mInsertTrackQuery.exec(); if (result && d->mInsertTrackQuery.isActive()) { d->mInsertTrackQuery.finish(); if (!isModifiedTrack) { ++d->mTrackId; } updateTrackOrigin(originTrackId, oneTrack.resourceURI(), oneTrack.fileModificationTime()); if (isModifiedTrack) { recordModifiedTrack(originTrackId); if (albumId != 0) { recordModifiedAlbum(albumId); } if (oldAlbumId != 0) { recordModifiedAlbum(oldAlbumId); } } if (albumId != 0) { if (updateAlbumFromId(albumId, covers[oneTrack.resourceURI().toString()], oneTrack, trackPath)) { auto modifiedTracks = fetchTrackIds(albumId); for (auto oneModifiedTrack : modifiedTracks) { if (oneModifiedTrack != resultId) { recordModifiedTrack(oneModifiedTrack); } } } recordModifiedAlbum(albumId); } } else { d->mInsertTrackQuery.finish(); Q_EMIT databaseError(); qDebug() << "DatabaseInterface::internalInsertTrack" << oneTrack << oneTrack.resourceURI(); qDebug() << "DatabaseInterface::internalInsertTrack" << d->mInsertTrackQuery.lastQuery(); qDebug() << "DatabaseInterface::internalInsertTrack" << d->mInsertTrackQuery.boundValues(); qDebug() << "DatabaseInterface::internalInsertTrack" << d->mInsertTrackQuery.lastError(); } } return resultId; } MusicAudioTrack DatabaseInterface::buildTrackFromDatabaseRecord(const QSqlRecord &trackRecord) const { auto id = trackRecord.value(0).toULongLong(); auto result = MusicAudioTrack{}; if (result.isValid()) { return result; } result.setDatabaseId(id); 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.setFileModificationTime(trackRecord.value(6).toDateTime()); result.setTrackNumber(trackRecord.value(7).toInt()); result.setDiscNumber(trackRecord.value(8).toInt()); result.setDuration(QTime::fromMSecsSinceStartOfDay(trackRecord.value(9).toInt())); result.setAlbumName(trackRecord.value(10).toString()); result.setRating(trackRecord.value(11).toInt()); result.setAlbumCover(trackRecord.value(12).toUrl()); result.setIsSingleDiscAlbum(trackRecord.value(13).toBool()); result.setGenre(trackRecord.value(14).toString()); result.setComposer(trackRecord.value(15).toString()); result.setLyricist(trackRecord.value(16).toString()); result.setComment(trackRecord.value(17).toString()); result.setYear(trackRecord.value(18).toInt()); result.setChannels(trackRecord.value(19).toInt()); result.setBitRate(trackRecord.value(20).toInt()); result.setSampleRate(trackRecord.value(21).toInt()); result.setAlbumId(trackRecord.value(2).toULongLong()); result.setHasEmbeddedCover(trackRecord.value(22).toBool()); result.setValid(true); return result; } DatabaseInterface::TrackDataType DatabaseInterface::buildTrackDataFromDatabaseRecord(const QSqlRecord &trackRecord) const { TrackDataType result; result[TrackDataType::key_type::DatabaseIdRole] = trackRecord.value(0); result[TrackDataType::key_type::TitleRole] = trackRecord.value(1); result[TrackDataType::key_type::AlbumRole] = trackRecord.value(10); result[TrackDataType::key_type::AlbumIdRole] = trackRecord.value(2); result[TrackDataType::key_type::ArtistRole] = trackRecord.value(3); result[TrackDataType::key_type::AlbumArtistRole] = trackRecord.value(4); result[TrackDataType::key_type::ResourceRole] = trackRecord.value(5); result[TrackDataType::key_type::TrackNumberRole] = trackRecord.value(7); result[TrackDataType::key_type::DiscNumberRole] = trackRecord.value(8); result[TrackDataType::key_type::DurationRole] = QTime::fromMSecsSinceStartOfDay(trackRecord.value(9).toInt()); result[TrackDataType::key_type::MilliSecondsDurationRole] = trackRecord.value(9).toInt(); result[TrackDataType::key_type::RatingRole] = trackRecord.value(11); result[TrackDataType::key_type::ImageUrlRole] = QUrl(trackRecord.value(12).toString()); result[TrackDataType::key_type::IsSingleDiscAlbumRole] = trackRecord.value(13); result[TrackDataType::key_type::GenreRole] = trackRecord.value(14); result[TrackDataType::key_type::ComposerRole] = trackRecord.value(15); result[TrackDataType::key_type::LyricistRole] = trackRecord.value(16); result[TrackDataType::key_type::HasEmbeddedCover] = trackRecord.value(22); result[TrackDataType::key_type::FileModificationTime] = trackRecord.value(6); return result; } void DatabaseInterface::internalRemoveTracksList(const QList &removedTracks) { for (const auto &removedTrackFileName : removedTracks) { d->mRemoveTracksMapping.bindValue(QStringLiteral(":fileName"), removedTrackFileName.toString()); auto result = d->mRemoveTracksMapping.exec(); if (!result || !d->mRemoveTracksMapping.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::internalRemoveTracksList" << d->mRemoveTracksMapping.lastQuery(); qDebug() << "DatabaseInterface::internalRemoveTracksList" << d->mRemoveTracksMapping.boundValues(); qDebug() << "DatabaseInterface::internalRemoveTracksList" << d->mRemoveTracksMapping.lastError(); continue; } d->mRemoveTracksMapping.finish(); } internalRemoveTracksWithoutMapping(); } void DatabaseInterface::internalRemoveTracksList(const QHash &removedTracks, qulonglong sourceId) { for (auto itRemovedTrack = removedTracks.begin(); itRemovedTrack != removedTracks.end(); ++itRemovedTrack) { d->mRemoveTracksMappingFromSource.bindValue(QStringLiteral(":fileName"), itRemovedTrack.key().toString()); d->mRemoveTracksMappingFromSource.bindValue(QStringLiteral(":sourceId"), sourceId); auto result = d->mRemoveTracksMappingFromSource.exec(); if (!result || !d->mRemoveTracksMappingFromSource.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::removeTracksList" << d->mRemoveTracksMappingFromSource.lastQuery(); qDebug() << "DatabaseInterface::removeTracksList" << d->mRemoveTracksMappingFromSource.boundValues(); qDebug() << "DatabaseInterface::removeTracksList" << d->mRemoveTracksMappingFromSource.lastError(); continue; } d->mRemoveTracksMappingFromSource.finish(); } internalRemoveTracksWithoutMapping(); } void DatabaseInterface::internalRemoveTracksWithoutMapping() { auto queryResult = d->mSelectTracksWithoutMappingQuery.exec(); if (!queryResult || !d->mSelectTracksWithoutMappingQuery.isSelect() || !d->mSelectTracksWithoutMappingQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertArtist" << d->mSelectTracksWithoutMappingQuery.lastQuery(); qDebug() << "DatabaseInterface::insertArtist" << d->mSelectTracksWithoutMappingQuery.boundValues(); qDebug() << "DatabaseInterface::insertArtist" << d->mSelectTracksWithoutMappingQuery.lastError(); d->mSelectTracksWithoutMappingQuery.finish(); return; } QList willRemoveTrack; while (d->mSelectTracksWithoutMappingQuery.next()) { const auto ¤tRecord = d->mSelectTracksWithoutMappingQuery.record(); willRemoveTrack.push_back(buildTrackFromDatabaseRecord(currentRecord)); } d->mSelectTracksWithoutMappingQuery.finish(); QSet modifiedAlbums; QUrl::FormattingOptions currentOptions = QUrl::PreferLocalFile | QUrl::RemoveAuthority | QUrl::RemoveFilename | QUrl::RemoveFragment | QUrl::RemovePassword | QUrl::RemovePort | QUrl::RemoveQuery | QUrl::RemoveScheme | QUrl::RemoveUserInfo; for (const auto &oneRemovedTrack : willRemoveTrack) { removeTrackInDatabase(oneRemovedTrack.databaseId()); Q_EMIT trackRemoved(oneRemovedTrack.databaseId()); const auto &modifiedAlbumId = internalAlbumIdFromTitleAndArtist(oneRemovedTrack.albumName(), oneRemovedTrack.albumArtist()); const auto &allTracksFromArtist = internalTracksFromAuthor(oneRemovedTrack.artist()); const auto &allAlbumsFromArtist = internalAlbumIdsFromAuthor(oneRemovedTrack.artist()); const auto &removedArtistId = internalArtistIdFromName(oneRemovedTrack.artist()); const auto &trackPath = oneRemovedTrack.resourceURI().toString(currentOptions); recordModifiedAlbum(modifiedAlbumId); modifiedAlbums.insert(modifiedAlbumId); updateAlbumFromId(modifiedAlbumId, oneRemovedTrack.albumCover(), oneRemovedTrack, trackPath); if (removedArtistId != 0 && allTracksFromArtist.isEmpty() && allAlbumsFromArtist.isEmpty()) { removeArtistInDatabase(removedArtistId); Q_EMIT artistRemoved(removedArtistId); } } for (auto modifiedAlbumId : modifiedAlbums) { const auto &modifiedAlbumData = internalOneAlbumPartialData(modifiedAlbumId); auto tracksCount = fetchTrackIds(modifiedAlbumId).count(); if (!modifiedAlbumData.isEmpty() && tracksCount) { Q_EMIT albumModified({{DatabaseIdRole, modifiedAlbumId}}, modifiedAlbumId); } else { removeAlbumInDatabase(modifiedAlbumId); Q_EMIT albumRemoved(modifiedAlbumId); const auto &allTracksFromArtist = internalTracksFromAuthor(modifiedAlbumData[AlbumDataType::key_type::ArtistRole].toString()); const auto &allAlbumsFromArtist = internalAlbumIdsFromAuthor(modifiedAlbumData[AlbumDataType::key_type::ArtistRole].toString()); const auto &removedArtistId = internalArtistIdFromName(modifiedAlbumData[AlbumDataType::key_type::ArtistRole].toString()); if (removedArtistId != 0 && allTracksFromArtist.isEmpty() && allAlbumsFromArtist.isEmpty()) { removeArtistInDatabase(removedArtistId); Q_EMIT artistRemoved(removedArtistId); } } } } QUrl DatabaseInterface::internalAlbumArtUriFromAlbumId(qulonglong albumId) { auto result = QUrl(); d->mSelectAlbumArtUriFromAlbumIdQuery.bindValue(QStringLiteral(":albumId"), albumId); auto queryResult = d->mSelectAlbumArtUriFromAlbumIdQuery.exec(); if (!queryResult || !d->mSelectAlbumArtUriFromAlbumIdQuery.isSelect() || !d->mSelectAlbumArtUriFromAlbumIdQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertArtist" << d->mSelectAlbumArtUriFromAlbumIdQuery.lastQuery(); qDebug() << "DatabaseInterface::insertArtist" << d->mSelectAlbumArtUriFromAlbumIdQuery.boundValues(); qDebug() << "DatabaseInterface::insertArtist" << d->mSelectAlbumArtUriFromAlbumIdQuery.lastError(); d->mSelectAlbumArtUriFromAlbumIdQuery.finish(); return result; } if (!d->mSelectAlbumArtUriFromAlbumIdQuery.next()) { d->mSelectAlbumArtUriFromAlbumIdQuery.finish(); return result; } result = d->mSelectAlbumArtUriFromAlbumIdQuery.record().value(0).toUrl(); d->mSelectAlbumArtUriFromAlbumIdQuery.finish(); return result; } bool DatabaseInterface::isValidArtist(qulonglong albumId) { auto result = false; d->mSelectAlbumQuery.bindValue(QStringLiteral(":albumId"), albumId); auto queryResult = d->mSelectAlbumQuery.exec(); if (!queryResult || !d->mSelectAlbumQuery.isSelect() || !d->mSelectAlbumQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::internalAlbumFromId" << d->mSelectAlbumQuery.lastQuery(); qDebug() << "DatabaseInterface::internalAlbumFromId" << d->mSelectAlbumQuery.boundValues(); qDebug() << "DatabaseInterface::internalAlbumFromId" << d->mSelectAlbumQuery.lastError(); d->mSelectAlbumQuery.finish(); return result; } if (!d->mSelectAlbumQuery.next()) { d->mSelectAlbumQuery.finish(); return result; } const auto ¤tRecord = d->mSelectAlbumQuery.record(); result = !currentRecord.value(3).toString().isEmpty(); return result; } qulonglong DatabaseInterface::internalSourceIdFromName(const QString &sourceName) { qulonglong sourceId = 0; d->mSelectMusicSource.bindValue(QStringLiteral(":name"), sourceName); auto queryResult = d->mSelectMusicSource.exec(); if (!queryResult || !d->mSelectMusicSource.isSelect() || !d->mSelectMusicSource.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mSelectMusicSource.lastQuery(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mSelectMusicSource.boundValues(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mSelectMusicSource.lastError(); d->mSelectMusicSource.finish(); return sourceId; } if (!d->mSelectMusicSource.next()) { return sourceId; } sourceId = d->mSelectMusicSource.record().value(0).toULongLong(); d->mSelectMusicSource.finish(); return sourceId; } QHash DatabaseInterface::internalAllFileNameFromSource(qulonglong sourceId) { QHash allFileNames; d->mSelectAllTrackFilesFromSourceQuery.bindValue(QStringLiteral(":discoverId"), sourceId); auto queryResult = d->mSelectAllTrackFilesFromSourceQuery.exec(); if (!queryResult || !d->mSelectAllTrackFilesFromSourceQuery.isSelect() || !d->mSelectAllTrackFilesFromSourceQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mSelectAllTrackFilesFromSourceQuery.lastQuery(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mSelectAllTrackFilesFromSourceQuery.boundValues(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mSelectAllTrackFilesFromSourceQuery.lastError(); d->mSelectAllTrackFilesFromSourceQuery.finish(); return allFileNames; } while(d->mSelectAllTrackFilesFromSourceQuery.next()) { auto fileName = d->mSelectAllTrackFilesFromSourceQuery.record().value(0).toUrl(); auto fileModificationTime = d->mSelectAllTrackFilesFromSourceQuery.record().value(1).toDateTime(); allFileNames[fileName] = fileModificationTime; } d->mSelectAllTrackFilesFromSourceQuery.finish(); return allFileNames; } bool DatabaseInterface::internalGenericPartialData(QSqlQuery &query) { auto result = false; auto queryResult = query.exec(); if (!queryResult || !query.isSelect() || !query.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::internalAllGenericPartialData" << query.lastQuery(); qDebug() << "DatabaseInterface::internalAllGenericPartialData" << query.boundValues(); qDebug() << "DatabaseInterface::internalAllGenericPartialData" << query.lastError(); query.finish(); auto transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } result = true; return result; } qulonglong DatabaseInterface::insertLyricist(const QString &name) { auto result = qulonglong(0); if (name.isEmpty()) { return result; } d->mSelectLyricistByNameQuery.bindValue(QStringLiteral(":name"), name); auto queryResult = d->mSelectLyricistByNameQuery.exec(); if (!queryResult || !d->mSelectLyricistByNameQuery.isSelect() || !d->mSelectLyricistByNameQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertLyricist" << d->mSelectLyricistByNameQuery.lastQuery(); qDebug() << "DatabaseInterface::insertLyricist" << d->mSelectLyricistByNameQuery.boundValues(); qDebug() << "DatabaseInterface::insertLyricist" << d->mSelectLyricistByNameQuery.lastError(); d->mSelectLyricistByNameQuery.finish(); return result; } if (d->mSelectLyricistByNameQuery.next()) { result = d->mSelectLyricistByNameQuery.record().value(0).toULongLong(); d->mSelectLyricistByNameQuery.finish(); return result; } d->mSelectLyricistByNameQuery.finish(); d->mInsertLyricistQuery.bindValue(QStringLiteral(":lyricistId"), d->mLyricistId); d->mInsertLyricistQuery.bindValue(QStringLiteral(":name"), name); queryResult = d->mInsertLyricistQuery.exec(); if (!queryResult || !d->mInsertLyricistQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertLyricist" << d->mInsertLyricistQuery.lastQuery(); qDebug() << "DatabaseInterface::insertLyricist" << d->mInsertLyricistQuery.boundValues(); qDebug() << "DatabaseInterface::insertLyricist" << d->mInsertLyricistQuery.lastError(); d->mInsertLyricistQuery.finish(); return result; } result = d->mLyricistId; ++d->mLyricistId; d->mInsertLyricistQuery.finish(); Q_EMIT lyricistsAdded(internalAllLyricistsPartialData()); return result; } qulonglong DatabaseInterface::internalArtistIdFromName(const QString &name) { auto result = qulonglong(0); if (name.isEmpty()) { return result; } d->mSelectArtistByNameQuery.bindValue(QStringLiteral(":name"), name); auto queryResult = d->mSelectArtistByNameQuery.exec(); if (!queryResult || !d->mSelectArtistByNameQuery.isSelect() || !d->mSelectArtistByNameQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertArtist" << d->mSelectArtistByNameQuery.lastQuery(); qDebug() << "DatabaseInterface::insertArtist" << d->mSelectArtistByNameQuery.boundValues(); qDebug() << "DatabaseInterface::insertArtist" << d->mSelectArtistByNameQuery.lastError(); d->mSelectArtistByNameQuery.finish(); return result; } if (!d->mSelectArtistByNameQuery.next()) { d->mSelectArtistByNameQuery.finish(); return result; } result = d->mSelectArtistByNameQuery.record().value(0).toULongLong(); d->mSelectArtistByNameQuery.finish(); return result; } void DatabaseInterface::removeTrackInDatabase(qulonglong trackId) { d->mRemoveTrackQuery.bindValue(QStringLiteral(":trackId"), trackId); auto result = d->mRemoveTrackQuery.exec(); if (!result || !d->mRemoveTrackQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::removeTrackInDatabase" << d->mRemoveTrackQuery.lastQuery(); qDebug() << "DatabaseInterface::removeTrackInDatabase" << d->mRemoveTrackQuery.boundValues(); qDebug() << "DatabaseInterface::removeTrackInDatabase" << d->mRemoveTrackQuery.lastError(); } d->mRemoveTrackQuery.finish(); } void DatabaseInterface::updateTrackInDatabase(const MusicAudioTrack &oneTrack, const QString &albumPath) { d->mUpdateTrackQuery.bindValue(QStringLiteral(":trackId"), oneTrack.databaseId()); d->mUpdateTrackQuery.bindValue(QStringLiteral(":title"), oneTrack.title()); insertArtist(oneTrack.artist()); d->mUpdateTrackQuery.bindValue(QStringLiteral(":artistName"), oneTrack.artist()); d->mUpdateTrackQuery.bindValue(QStringLiteral(":albumTitle"), oneTrack.albumName()); if (oneTrack.isValidAlbumArtist()) { d->mUpdateTrackQuery.bindValue(QStringLiteral(":albumArtistName"), oneTrack.albumArtist()); } else { d->mUpdateTrackQuery.bindValue(QStringLiteral(":albumArtistName"), {}); } d->mUpdateTrackQuery.bindValue(QStringLiteral(":albumPath"), albumPath); d->mUpdateTrackQuery.bindValue(QStringLiteral(":trackNumber"), oneTrack.trackNumber()); d->mUpdateTrackQuery.bindValue(QStringLiteral(":discNumber"), oneTrack.discNumber()); d->mUpdateTrackQuery.bindValue(QStringLiteral(":trackDuration"), QVariant::fromValue(oneTrack.duration().msecsSinceStartOfDay())); d->mUpdateTrackQuery.bindValue(QStringLiteral(":trackRating"), oneTrack.rating()); auto genreId = insertGenre(oneTrack.genre()); if (genreId != 0) { d->mUpdateTrackQuery.bindValue(QStringLiteral(":genreId"), genreId); } else { d->mUpdateTrackQuery.bindValue(QStringLiteral(":genreId"), {}); } auto composerId = insertComposer(oneTrack.composer()); if (composerId != 0) { d->mUpdateTrackQuery.bindValue(QStringLiteral(":composerId"), composerId); } else { d->mUpdateTrackQuery.bindValue(QStringLiteral(":composerId"), {}); } auto lyricistId = insertLyricist(oneTrack.lyricist()); if (lyricistId != 0) { d->mUpdateTrackQuery.bindValue(QStringLiteral(":lyricistId"), lyricistId); } else { d->mUpdateTrackQuery.bindValue(QStringLiteral(":lyricistId"), {}); } d->mUpdateTrackQuery.bindValue(QStringLiteral(":comment"), oneTrack.comment()); d->mUpdateTrackQuery.bindValue(QStringLiteral(":year"), oneTrack.year()); d->mUpdateTrackQuery.bindValue(QStringLiteral(":channels"), oneTrack.channels()); d->mUpdateTrackQuery.bindValue(QStringLiteral(":bitRate"), oneTrack.bitRate()); d->mUpdateTrackQuery.bindValue(QStringLiteral(":sampleRate"), oneTrack.sampleRate()); auto result = d->mUpdateTrackQuery.exec(); if (!result || !d->mUpdateTrackQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::updateTrackInDatabase" << d->mUpdateTrackQuery.lastQuery(); qDebug() << "DatabaseInterface::updateTrackInDatabase" << d->mUpdateTrackQuery.boundValues(); qDebug() << "DatabaseInterface::updateTrackInDatabase" << d->mUpdateTrackQuery.lastError(); } d->mUpdateTrackQuery.finish(); } void DatabaseInterface::removeAlbumInDatabase(qulonglong albumId) { d->mRemoveAlbumQuery.bindValue(QStringLiteral(":albumId"), albumId); auto result = d->mRemoveAlbumQuery.exec(); if (!result || !d->mRemoveAlbumQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::removeAlbumInDatabase" << d->mRemoveAlbumQuery.lastQuery(); qDebug() << "DatabaseInterface::removeAlbumInDatabase" << d->mRemoveAlbumQuery.boundValues(); qDebug() << "DatabaseInterface::removeAlbumInDatabase" << d->mRemoveAlbumQuery.lastError(); } d->mRemoveAlbumQuery.finish(); } void DatabaseInterface::removeArtistInDatabase(qulonglong artistId) { d->mRemoveArtistQuery.bindValue(QStringLiteral(":artistId"), artistId); auto result = d->mRemoveArtistQuery.exec(); if (!result || !d->mRemoveArtistQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::removeArtistInDatabase" << d->mRemoveArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::removeArtistInDatabase" << d->mRemoveArtistQuery.boundValues(); qDebug() << "DatabaseInterface::removeArtistInDatabase" << d->mRemoveArtistQuery.lastError(); } d->mRemoveArtistQuery.finish(); } void DatabaseInterface::reloadExistingDatabase() { qDebug() << "DatabaseInterface::reloadExistingDatabase"; d->mArtistId = initialId(DataUtils::DataType::AllArtists); d->mComposerId = initialId(DataUtils::DataType::AllComposers); d->mLyricistId = initialId(DataUtils::DataType::AllLyricists); d->mAlbumId = initialId(DataUtils::DataType::AllAlbums); d->mTrackId = initialId(DataUtils::DataType::AllTracks); d->mGenreId = initialId(DataUtils::DataType::AllGenres);; } qulonglong DatabaseInterface::initialId(DataUtils::DataType aType) { switch (aType) { case DataUtils::DataType::AllAlbums: return genericInitialId(d->mQueryMaximumAlbumIdQuery); case DataUtils::DataType::AllArtists: return genericInitialId(d->mQueryMaximumArtistIdQuery); case DataUtils::DataType::AllComposers: return genericInitialId(d->mQueryMaximumComposerIdQuery); case DataUtils::DataType::AllGenres: return genericInitialId(d->mQueryMaximumGenreIdQuery); case DataUtils::DataType::AllLyricists: return genericInitialId(d->mQueryMaximumLyricistIdQuery); case DataUtils::DataType::AllTracks: return genericInitialId(d->mQueryMaximumTrackIdQuery); case DataUtils::DataType::UnknownType: break; } return 1; } qulonglong DatabaseInterface::genericInitialId(QSqlQuery &request) { auto result = qulonglong(0); auto transactionResult = startTransaction(); if (!transactionResult) { return result; } auto queryResult = request.exec(); if (!queryResult || !request.isSelect() || !request.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertMusicSource" << request.lastQuery(); qDebug() << "DatabaseInterface::insertMusicSource" << request.boundValues(); qDebug() << "DatabaseInterface::insertMusicSource" << request.lastError(); request.finish(); transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } if (request.next()) { result = request.record().value(0).toULongLong() + 1; request.finish(); } transactionResult = finishTransaction(); if (!transactionResult) { return result; } return result; } qulonglong DatabaseInterface::insertMusicSource(const QString &name) { qulonglong result = 0; d->mSelectMusicSource.bindValue(QStringLiteral(":name"), name); auto queryResult = d->mSelectMusicSource.exec(); if (!queryResult || !d->mSelectMusicSource.isSelect() || !d->mSelectMusicSource.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mSelectMusicSource.lastQuery(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mSelectMusicSource.boundValues(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mSelectMusicSource.lastError(); d->mSelectMusicSource.finish(); return result; } if (d->mSelectMusicSource.next()) { result = d->mSelectMusicSource.record().value(0).toULongLong(); d->mSelectMusicSource.finish(); return result; } d->mSelectMusicSource.finish(); d->mInsertMusicSource.bindValue(QStringLiteral(":discoverId"), d->mDiscoverId); d->mInsertMusicSource.bindValue(QStringLiteral(":name"), name); queryResult = d->mInsertMusicSource.exec(); if (!queryResult || !d->mInsertMusicSource.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mInsertMusicSource.lastQuery(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mInsertMusicSource.boundValues(); qDebug() << "DatabaseInterface::insertMusicSource" << d->mInsertMusicSource.lastError(); d->mInsertMusicSource.finish(); return d->mDiscoverId; } d->mInsertMusicSource.finish(); ++d->mDiscoverId; return d->mDiscoverId - 1; } QList DatabaseInterface::fetchTrackIds(qulonglong albumId) { auto allTracks = QList(); d->mSelectTrackQuery.bindValue(QStringLiteral(":albumId"), albumId); auto result = d->mSelectTrackQuery.exec(); if (!result || !d->mSelectTrackQuery.isSelect() || !d->mSelectTrackQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::fetchTrackIds" << d->mSelectTrackQuery.lastQuery(); qDebug() << "DatabaseInterface::fetchTrackIds" << d->mSelectTrackQuery.boundValues(); qDebug() << "DatabaseInterface::fetchTrackIds" << d->mSelectTrackQuery.lastError(); } while (d->mSelectTrackQuery.next()) { const auto ¤tRecord = d->mSelectTrackQuery.record(); allTracks.push_back(currentRecord.value(0).toULongLong()); } d->mSelectTrackQuery.finish(); return allTracks; } qulonglong DatabaseInterface::internalAlbumIdFromTitleAndArtist(const QString &title, const QString &artist) { auto result = qulonglong(0); d->mSelectAlbumIdFromTitleQuery.bindValue(QStringLiteral(":title"), title); d->mSelectAlbumIdFromTitleQuery.bindValue(QStringLiteral(":artistName"), artist); auto queryResult = d->mSelectAlbumIdFromTitleQuery.exec(); if (!queryResult || !d->mSelectAlbumIdFromTitleQuery.isSelect() || !d->mSelectAlbumIdFromTitleQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::internalAlbumIdFromTitleAndArtist" << d->mSelectAlbumIdFromTitleQuery.lastQuery(); qDebug() << "DatabaseInterface::internalAlbumIdFromTitleAndArtist" << d->mSelectAlbumIdFromTitleQuery.boundValues(); qDebug() << "DatabaseInterface::internalAlbumIdFromTitleAndArtist" << d->mSelectAlbumIdFromTitleQuery.lastError(); d->mSelectAlbumIdFromTitleQuery.finish(); return result; } if (d->mSelectAlbumIdFromTitleQuery.next()) { result = d->mSelectAlbumIdFromTitleQuery.record().value(0).toULongLong(); } d->mSelectAlbumIdFromTitleQuery.finish(); if (result == 0) { d->mSelectAlbumIdFromTitleWithoutArtistQuery.bindValue(QStringLiteral(":title"), title); auto queryResult = d->mSelectAlbumIdFromTitleWithoutArtistQuery.exec(); if (!queryResult || !d->mSelectAlbumIdFromTitleWithoutArtistQuery.isSelect() || !d->mSelectAlbumIdFromTitleWithoutArtistQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::internalAlbumIdFromTitleAndArtist" << d->mSelectAlbumIdFromTitleWithoutArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::internalAlbumIdFromTitleAndArtist" << d->mSelectAlbumIdFromTitleWithoutArtistQuery.boundValues(); qDebug() << "DatabaseInterface::internalAlbumIdFromTitleAndArtist" << d->mSelectAlbumIdFromTitleWithoutArtistQuery.lastError(); d->mSelectAlbumIdFromTitleWithoutArtistQuery.finish(); return result; } if (d->mSelectAlbumIdFromTitleWithoutArtistQuery.next()) { result = d->mSelectAlbumIdFromTitleWithoutArtistQuery.record().value(0).toULongLong(); } d->mSelectAlbumIdFromTitleWithoutArtistQuery.finish(); } return result; } MusicAudioTrack DatabaseInterface::internalTrackFromDatabaseId(qulonglong id) { auto result = MusicAudioTrack(); if (result.isValid()) { return result; } if (!d || !d->mTracksDatabase.isValid() || !d->mInitFinished) { return result; } d->mSelectTrackFromIdQuery.bindValue(QStringLiteral(":trackId"), id); auto queryResult = d->mSelectTrackFromIdQuery.exec(); if (!queryResult || !d->mSelectTrackFromIdQuery.isSelect() || !d->mSelectTrackFromIdQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::internalTrackFromDatabaseId" << d->mSelectAlbumQuery.lastQuery(); qDebug() << "DatabaseInterface::internalTrackFromDatabaseId" << d->mSelectAlbumQuery.boundValues(); qDebug() << "DatabaseInterface::internalTrackFromDatabaseId" << d->mSelectAlbumQuery.lastError(); d->mSelectTrackFromIdQuery.finish(); return result; } if (!d->mSelectTrackFromIdQuery.next()) { d->mSelectTrackFromIdQuery.finish(); return result; } const auto ¤tRecord = d->mSelectTrackFromIdQuery.record(); result = buildTrackFromDatabaseRecord(currentRecord); d->mSelectTrackFromIdQuery.finish(); return result; } qulonglong DatabaseInterface::internalTrackIdFromTitleAlbumTracDiscNumber(const QString &title, const QString &artist, const QString &album, int trackNumber, int discNumber) { auto result = qulonglong(0); if (!d) { return result; } d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.bindValue(QStringLiteral(":title"), title); d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.bindValue(QStringLiteral(":artist"), artist); d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.bindValue(QStringLiteral(":album"), album); d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.bindValue(QStringLiteral(":trackNumber"), trackNumber); d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.bindValue(QStringLiteral(":discNumber"), discNumber); auto queryResult = d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.exec(); if (!queryResult || !d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.isSelect() || !d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::trackIdFromTitleAlbumArtist" << d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.lastQuery(); qDebug() << "DatabaseInterface::trackIdFromTitleAlbumArtist" << d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.boundValues(); qDebug() << "DatabaseInterface::trackIdFromTitleAlbumArtist" << d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.lastError(); d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.finish(); return result; } if (d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.next()) { result = d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.record().value(0).toULongLong(); } d->mSelectTrackIdFromTitleArtistAlbumTrackDiscNumberQuery.finish(); return result; } qulonglong DatabaseInterface::getDuplicateTrackIdFromTitleAlbumTrackDiscNumber(const QString &title, const QString &trackArtist, const QString &album, const QString &albumArtist, const QString &trackPath, int trackNumber, int discNumber) { auto result = qulonglong(0); if (!d) { return result; } d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.bindValue(QStringLiteral(":title"), title); d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.bindValue(QStringLiteral(":trackArtist"), trackArtist); d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.bindValue(QStringLiteral(":album"), album); d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.bindValue(QStringLiteral(":albumPath"), trackPath); d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.bindValue(QStringLiteral(":albumArtist"), albumArtist); d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.bindValue(QStringLiteral(":trackNumber"), trackNumber); d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.bindValue(QStringLiteral(":discNumber"), discNumber); auto queryResult = d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.exec(); if (!queryResult || !d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.isSelect() || !d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::trackIdFromTitleAlbumArtist" << d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.lastQuery(); qDebug() << "DatabaseInterface::trackIdFromTitleAlbumArtist" << d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.boundValues(); qDebug() << "DatabaseInterface::trackIdFromTitleAlbumArtist" << d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.lastError(); d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.finish(); return result; } if (d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.next()) { result = d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.record().value(0).toULongLong(); } d->mSelectTrackIdFromTitleAlbumTrackDiscNumberQuery.finish(); return result; } qulonglong DatabaseInterface::internalTrackIdFromFileName(const QUrl &fileName) { auto result = qulonglong(0); if (!d) { return result; } d->mSelectTracksMapping.bindValue(QStringLiteral(":fileName"), fileName); auto queryResult = d->mSelectTracksMapping.exec(); if (!queryResult || !d->mSelectTracksMapping.isSelect() || !d->mSelectTracksMapping.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::internalTrackIdFromFileName" << d->mSelectTracksMapping.lastQuery(); qDebug() << "DatabaseInterface::internalTrackIdFromFileName" << d->mSelectTracksMapping.boundValues(); qDebug() << "DatabaseInterface::internalTrackIdFromFileName" << d->mSelectTracksMapping.lastError(); d->mSelectTracksMapping.finish(); return result; } if (d->mSelectTracksMapping.next()) { const auto ¤tRecordValue = d->mSelectTracksMapping.record().value(0); if (currentRecordValue.isValid()) { result = currentRecordValue.toULongLong(); } } d->mSelectTracksMapping.finish(); return result; } DatabaseInterface::ListTrackDataType DatabaseInterface::internalTracksFromAuthor(const QString &ArtistName) { auto allTracks = ListTrackDataType{}; d->mSelectTracksFromArtist.bindValue(QStringLiteral(":artistName"), ArtistName); auto result = d->mSelectTracksFromArtist.exec(); if (!result || !d->mSelectTracksFromArtist.isSelect() || !d->mSelectTracksFromArtist.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::tracksFromAuthor" << d->mSelectTracksFromArtist.lastQuery(); qDebug() << "DatabaseInterface::tracksFromAuthor" << d->mSelectTracksFromArtist.boundValues(); qDebug() << "DatabaseInterface::tracksFromAuthor" << d->mSelectTracksFromArtist.lastError(); return allTracks; } while (d->mSelectTracksFromArtist.next()) { const auto ¤tRecord = d->mSelectTracksFromArtist.record(); allTracks.push_back(buildTrackDataFromDatabaseRecord(currentRecord)); } d->mSelectTracksFromArtist.finish(); return allTracks; } QList DatabaseInterface::internalAlbumIdsFromAuthor(const QString &ArtistName) { auto allAlbumIds = QList(); d->mSelectAlbumIdsFromArtist.bindValue(QStringLiteral(":artistName"), ArtistName); auto result = d->mSelectAlbumIdsFromArtist.exec(); if (!result || !d->mSelectAlbumIdsFromArtist.isSelect() || !d->mSelectAlbumIdsFromArtist.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::tracksFromAuthor" << d->mSelectAlbumIdsFromArtist.lastQuery(); qDebug() << "DatabaseInterface::tracksFromAuthor" << d->mSelectAlbumIdsFromArtist.boundValues(); qDebug() << "DatabaseInterface::tracksFromAuthor" << d->mSelectAlbumIdsFromArtist.lastError(); return allAlbumIds; } while (d->mSelectAlbumIdsFromArtist.next()) { const auto ¤tRecord = d->mSelectAlbumIdsFromArtist.record(); allAlbumIds.push_back(currentRecord.value(0).toULongLong()); } d->mSelectAlbumIdsFromArtist.finish(); return allAlbumIds; } DatabaseInterface::ListArtistDataType DatabaseInterface::internalAllArtistsPartialData(QSqlQuery &artistsQuery) { auto result = ListArtistDataType{}; if (!internalGenericPartialData(artistsQuery)) { return result; } while(artistsQuery.next()) { auto newData = ArtistDataType{}; const auto ¤tRecord = artistsQuery.record(); newData[DataType::key_type::DatabaseIdRole] = currentRecord.value(0); newData[DataType::key_type::TitleRole] = currentRecord.value(1); newData[DataType::key_type::GenreRole] = QVariant::fromValue(currentRecord.value(2).toString().split(QStringLiteral(", "))); result.push_back(newData); } artistsQuery.finish(); return result; } DatabaseInterface::ArtistDataType DatabaseInterface::internalOneArtistPartialData(qulonglong databaseId) { auto result = ArtistDataType{}; d->mSelectArtistQuery.bindValue(QStringLiteral(":artistId"), databaseId); if (!internalGenericPartialData(d->mSelectArtistQuery)) { return result; } if (d->mSelectArtistQuery.next()) { const auto ¤tRecord = d->mSelectArtistQuery.record(); result[DataType::key_type::DatabaseIdRole] = currentRecord.value(0); result[DataType::key_type::TitleRole] = currentRecord.value(1); result[DataType::key_type::GenreRole] = QVariant::fromValue(currentRecord.value(2).toString().split(QStringLiteral(", "))); } d->mSelectArtistQuery.finish(); return result; } DatabaseInterface::ListAlbumDataType DatabaseInterface::internalAllAlbumsPartialData(QSqlQuery &query) { auto result = ListAlbumDataType{}; if (!internalGenericPartialData(query)) { return result; } while(query.next()) { auto newData = AlbumDataType{}; const auto ¤tRecord = query.record(); newData[DataType::key_type::DatabaseIdRole] = currentRecord.value(0); newData[DataType::key_type::TitleRole] = currentRecord.value(1); newData[DataType::key_type::SecondaryTextRole] = currentRecord.value(2); newData[DataType::key_type::ImageUrlRole] = currentRecord.value(3); newData[DataType::key_type::ArtistRole] = currentRecord.value(4); newData[DataType::key_type::AllArtistsRole] = QVariant::fromValue(currentRecord.value(5).toString().split(QStringLiteral(", "))); newData[DataType::key_type::HighestTrackRating] = currentRecord.value(6); newData[DataType::key_type::IsSingleDiscAlbumRole] = currentRecord.value(8); newData[DataType::key_type::GenreRole] = QVariant::fromValue(currentRecord.value(7).toString().split(QStringLiteral(", "))); result.push_back(newData); } query.finish(); return result; } DatabaseInterface::AlbumDataType DatabaseInterface::internalOneAlbumPartialData(qulonglong databaseId) { auto result = AlbumDataType{}; d->mSelectAlbumQuery.bindValue(QStringLiteral(":albumId"), databaseId); if (!internalGenericPartialData(d->mSelectAlbumQuery)) { return result; } if (d->mSelectAlbumQuery.next()) { const auto ¤tRecord = d->mSelectAlbumQuery.record(); result[DataType::key_type::DatabaseIdRole] = currentRecord.value(0); result[DataType::key_type::TitleRole] = currentRecord.value(1); result[DataType::key_type::SecondaryTextRole] = currentRecord.value(3); result[DataType::key_type::ImageUrlRole] = currentRecord.value(5); result[DataType::key_type::ArtistRole] = currentRecord.value(3); result[DataType::key_type::AllArtistsRole] = QVariant::fromValue(currentRecord.value(9).toString().split(QStringLiteral(", "))); result[DataType::key_type::HighestTrackRating] = currentRecord.value(10); result[DataType::key_type::IsSingleDiscAlbumRole] = currentRecord.value(8); result[DataType::key_type::GenreRole] = QVariant::fromValue(currentRecord.value(11).toString().split(QStringLiteral(", "))); } d->mSelectAlbumQuery.finish(); return result; } DatabaseInterface::ListTrackDataType DatabaseInterface::internalAllTracksPartialData() { auto result = ListTrackDataType{}; if (!internalGenericPartialData(d->mSelectAllTracksShortQuery)) { return result; } while(d->mSelectAllTracksShortQuery.next()) { auto newData = TrackDataType{}; const auto ¤tRecord = d->mSelectAllTracksShortQuery.record(); newData[TrackDataType::key_type::DatabaseIdRole] = currentRecord.value(0); newData[TrackDataType::key_type::TitleRole] = currentRecord.value(1); newData[TrackDataType::key_type::ArtistRole] = currentRecord.value(2); newData[TrackDataType::key_type::AlbumRole] = currentRecord.value(3); newData[TrackDataType::key_type::AlbumArtistRole] = currentRecord.value(4); newData[TrackDataType::key_type::DurationRole] = QTime::fromMSecsSinceStartOfDay(currentRecord.value(5).toInt()); newData[TrackDataType::key_type::MilliSecondsDurationRole] = currentRecord.value(5); newData[TrackDataType::key_type::ImageUrlRole] = currentRecord.value(6); newData[TrackDataType::key_type::TrackNumberRole] = currentRecord.value(7); newData[TrackDataType::key_type::DiscNumberRole] = currentRecord.value(8); newData[TrackDataType::key_type::RatingRole] = currentRecord.value(9); newData[TrackDataType::key_type::IsSingleDiscAlbumRole] = currentRecord.value(13); result.push_back(newData); } d->mSelectAllTracksShortQuery.finish(); return result; } DatabaseInterface::TrackDataType DatabaseInterface::internalOneTrackPartialData(qulonglong databaseId) { auto result = TrackDataType{}; d->mSelectTrackFromIdQuery.bindValue(QStringLiteral(":trackId"), databaseId); if (!internalGenericPartialData(d->mSelectTrackFromIdQuery)) { return result; } if (d->mSelectTrackFromIdQuery.next()) { const auto ¤tRecord = d->mSelectTrackFromIdQuery.record(); result = buildTrackDataFromDatabaseRecord(currentRecord); } d->mSelectTrackFromIdQuery.finish(); return result; } DatabaseInterface::ListGenreDataType DatabaseInterface::internalAllGenresPartialData() { ListGenreDataType result; if (!internalGenericPartialData(d->mSelectAllGenresQuery)) { return result; } while(d->mSelectAllGenresQuery.next()) { auto newData = GenreDataType{}; const auto ¤tRecord = d->mSelectAllGenresQuery.record(); newData[DataType::key_type::DatabaseIdRole] = currentRecord.value(0); newData[DataType::key_type::TitleRole] = currentRecord.value(1); result.push_back(newData); } d->mSelectAllGenresQuery.finish(); return result; } DatabaseInterface::GenreDataType DatabaseInterface::internalOneGenrePartialData(qulonglong databaseId) { auto result = GenreDataType{}; d->mSelectGenreQuery.bindValue(QStringLiteral(":genreId"), databaseId); if (!internalGenericPartialData(d->mSelectGenreQuery)) { return result; } if (d->mSelectGenreQuery.next()) { const auto ¤tRecord = d->mSelectGenreQuery.record(); result[DataType::key_type::DatabaseIdRole] = currentRecord.value(0); result[DataType::key_type::TitleRole] = currentRecord.value(1); } d->mSelectGenreQuery.finish(); return result; } DatabaseInterface::ListArtistDataType DatabaseInterface::internalAllComposersPartialData() { ListArtistDataType result; if (!internalGenericPartialData(d->mSelectAllComposersQuery)) { return result; } while(d->mSelectAllComposersQuery.next()) { auto newData = ArtistDataType{}; const auto ¤tRecord = d->mSelectAllComposersQuery.record(); newData[DataType::key_type::DatabaseIdRole] = currentRecord.value(0); newData[DataType::key_type::TitleRole] = currentRecord.value(1); result.push_back(newData); } d->mSelectAllComposersQuery.finish(); return result; } DatabaseInterface::ArtistDataType DatabaseInterface::internalOneComposerPartialData(qulonglong databaseId) { auto result = ArtistDataType{}; d->mSelectComposerQuery.bindValue(QStringLiteral(":composerId"), databaseId); if (!internalGenericPartialData(d->mSelectComposerQuery)) { return result; } if (d->mSelectComposerQuery.next()) { const auto ¤tRecord = d->mSelectComposerQuery.record(); result[DataType::key_type::DatabaseIdRole] = currentRecord.value(0); result[DataType::key_type::TitleRole] = currentRecord.value(1); } d->mSelectComposerQuery.finish(); return result; } DatabaseInterface::ListArtistDataType DatabaseInterface::internalAllLyricistsPartialData() { ListArtistDataType result; if (!internalGenericPartialData(d->mSelectAllLyricistsQuery)) { return result; } while(d->mSelectAllLyricistsQuery.next()) { auto newData = ArtistDataType{}; const auto ¤tRecord = d->mSelectAllLyricistsQuery.record(); newData[DataType::key_type::DatabaseIdRole] = currentRecord.value(0); newData[DataType::key_type::TitleRole] = currentRecord.value(1); result.push_back(newData); } d->mSelectAllLyricistsQuery.finish(); return result; } DatabaseInterface::ArtistDataType DatabaseInterface::internalOneLyricistPartialData(qulonglong databaseId) { auto result = ArtistDataType{}; d->mSelectLyricistQuery.bindValue(QStringLiteral(":lyricistId"), databaseId); if (!internalGenericPartialData(d->mSelectLyricistQuery)) { return result; } if (d->mSelectLyricistQuery.next()) { const auto ¤tRecord = d->mSelectLyricistQuery.record(); result[DataType::key_type::DatabaseIdRole] = currentRecord.value(0); result[DataType::key_type::TitleRole] = currentRecord.value(1); } d->mSelectLyricistQuery.finish(); return result; } bool DatabaseInterface::prepareQuery(QSqlQuery &query, const QString &queryText) const { query.setForwardOnly(true); return query.prepare(queryText); } void DatabaseInterface::updateAlbumArtist(qulonglong albumId, const QString &title, const QString &albumPath, const QString &artistName) { d->mUpdateAlbumArtistQuery.bindValue(QStringLiteral(":albumId"), albumId); insertArtist(artistName); d->mUpdateAlbumArtistQuery.bindValue(QStringLiteral(":artistName"), artistName); auto queryResult = d->mUpdateAlbumArtistQuery.exec(); if (!queryResult || !d->mUpdateAlbumArtistQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::updateAlbumArtist" << d->mUpdateAlbumArtistQuery.lastQuery(); qDebug() << "DatabaseInterface::updateAlbumArtist" << d->mUpdateAlbumArtistQuery.boundValues(); qDebug() << "DatabaseInterface::updateAlbumArtist" << d->mUpdateAlbumArtistQuery.lastError(); d->mUpdateAlbumArtistQuery.finish(); return; } d->mUpdateAlbumArtistQuery.finish(); d->mUpdateAlbumArtistInTracksQuery.bindValue(QStringLiteral(":albumTitle"), title); d->mUpdateAlbumArtistInTracksQuery.bindValue(QStringLiteral(":albumPath"), albumPath); d->mUpdateAlbumArtistInTracksQuery.bindValue(QStringLiteral(":artistName"), artistName); queryResult = d->mUpdateAlbumArtistInTracksQuery.exec(); if (!queryResult || !d->mUpdateAlbumArtistInTracksQuery.isActive()) { Q_EMIT databaseError(); qDebug() << "DatabaseInterface::updateAlbumArtist" << d->mUpdateAlbumArtistInTracksQuery.lastQuery(); qDebug() << "DatabaseInterface::updateAlbumArtist" << d->mUpdateAlbumArtistInTracksQuery.boundValues(); qDebug() << "DatabaseInterface::updateAlbumArtist" << d->mUpdateAlbumArtistInTracksQuery.lastError(); d->mUpdateAlbumArtistInTracksQuery.finish(); return; } d->mUpdateAlbumArtistInTracksQuery.finish(); } #include "moc_databaseinterface.cpp" diff --git a/src/main.cpp b/src/main.cpp index f3bf6a78..21399d9c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,164 +1,181 @@ /* * Copyright 2015-2018 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "config-upnp-qt.h" #include "elisaapplication.h" #include "elisa_settings.h" //#define QT_QML_DEBUG #if defined KF5Declarative_FOUND && KF5Declarative_FOUND #include #endif #include #include #include #if defined KF5Crash_FOUND && KF5Crash_FOUND #include #endif #if defined KF5DBusAddons_FOUND && KF5DBusAddons_FOUND #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include +#if defined Qt5AndroidExtras_FOUND && Qt5AndroidExtras_FOUND +#include +#endif + #include #if defined Qt5AndroidExtras_FOUND && Qt5AndroidExtras_FOUND #include #include #endif #if defined Q_OS_ANDROID int __attribute__((visibility("default"))) main(int argc, char *argv[]) #else int main(int argc, char *argv[]) #endif { +#if defined Q_OS_ANDROID + if(argc > 1 && strcmp(argv[1], "-service") == 0){ + QAndroidService app(argc, argv); + qInfo() << "Service starting..."; + + // My service stuff + + return app.exec(); + } + + qInfo() << "Application starting..."; +#endif + QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); qputenv("QT_GSTREAMER_USE_PLAYBIN_VOLUME", "true"); QApplication app(argc, argv); +#if defined Qt5AndroidExtras_FOUND && Qt5AndroidExtras_FOUND + qInfo() << QCoreApplication::arguments(); + + QAndroidJniObject::callStaticMethod("org/kde/elisa/ElisaService", + "startMyService", + "(Landroid/content/Context;)V", + QtAndroid::androidContext().object()); +#endif + #if defined KF5Crash_FOUND && KF5Crash_FOUND KCrash::initialize(); #endif QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("elisa"))); KLocalizedString::setApplicationDomain("elisa"); KAboutData aboutData( QStringLiteral("elisa"), i18n("Elisa"), QStringLiteral("0.3.80"), i18n("A Simple Music Player written with KDE Frameworks"), KAboutLicense::LGPL_V3, i18n("(c) 2015-2018, Elisa contributors")); aboutData.addAuthor(QStringLiteral("Matthieu Gallien"),i18n("Creator"), QStringLiteral("mgallien@mgallien.fr")); aboutData.addAuthor(QStringLiteral("Alexander Stippich"), i18n("Author"), QStringLiteral("a.stippich@gmx.net")); aboutData.addCredit(QStringLiteral("Andrew Lake"), i18n("Concept and design work"), QStringLiteral("jamboarder@gmail.com")); aboutData.addCredit(QStringLiteral("Luigi Toscano"), i18n("Localization support"), QStringLiteral("luigi.toscano@tiscali.it")); aboutData.addCredit(QStringLiteral("Safa Alfulaij"), i18n("Right to left support in interface"), QStringLiteral("safa1996alfulaij@gmail.com")); aboutData.addCredit(QStringLiteral("Diego Gangl"), i18n("Various improvements to the interface"), QStringLiteral("diego@sinestesia.co")); KAboutData::setApplicationData(aboutData); KLocalizedString::setApplicationDomain("elisa"); QCommandLineParser parser; parser.addHelpOption(); parser.addVersionOption(); aboutData.setupCommandLine(&parser); parser.process(app); aboutData.processCommandLine(&parser); -#if defined Qt5AndroidExtras_FOUND && Qt5AndroidExtras_FOUND - qDebug() << QCoreApplication::arguments(); - - QAndroidJniObject::callStaticMethod("com/kde/elisa/ElisaService", - "startMyService", - "(Landroid/content/Context;)V", - QtAndroid::androidContext().object()); -#endif - QQuickStyle::setStyle(QStringLiteral("org.kde.desktop")); QQuickStyle::setFallbackStyle(QStringLiteral("Fusion")); QQmlApplicationEngine engine; engine.addImportPath(QStringLiteral("qrc:/imports")); QQmlFileSelector selector(&engine); #if defined KF5Declarative_FOUND && KF5Declarative_FOUND KDeclarative::KDeclarative decl; decl.setDeclarativeEngine(&engine); decl.setupEngine(&engine); decl.setupContext(); #endif engine.rootContext()->setContextObject(new KLocalizedContext(&engine)); engine.rootContext()->setContextProperty(QStringLiteral("logicalDpi"), QGuiApplication::primaryScreen()->logicalDotsPerInch()); #if defined KF5DBusAddons_FOUND && KF5DBusAddons_FOUND KDBusService elisaService(KDBusService::Unique); #endif std::unique_ptr myApp = std::make_unique(); #if defined KF5DBusAddons_FOUND && KF5DBusAddons_FOUND QObject::connect(&elisaService, &KDBusService::activateActionRequested, myApp.get(), &ElisaApplication::activateActionRequested); QObject::connect(&elisaService, &KDBusService::activateRequested, myApp.get(), &ElisaApplication::activateRequested); QObject::connect(&elisaService, &KDBusService::openRequested, myApp.get(), &ElisaApplication::openRequested); #endif auto arguments = ElisaUtils::EntryDataList{}; auto realArgumentsList = parser.positionalArguments(); for (auto oneArgument : realArgumentsList) { arguments.push_back({0, oneArgument}); } myApp->setArguments(arguments); engine.rootContext()->setContextProperty(QStringLiteral("elisa"), myApp.release()); engine.load(QUrl(QStringLiteral("qrc:/qml/ElisaMainWindow.qml"))); return app.exec(); } diff --git a/src/musiclistenersmanager.cpp b/src/musiclistenersmanager.cpp index 90abbea5..8a792032 100644 --- a/src/musiclistenersmanager.cpp +++ b/src/musiclistenersmanager.cpp @@ -1,393 +1,421 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #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 +#if defined Qt5AndroidExtras_FOUND && Qt5AndroidExtras_FOUND +#include "android/androidmusiclistener.h" +#endif + #include "databaseinterface.h" #include "mediaplaylist.h" #include "file/filelistener.h" #include "file/localfilelisting.h" #include "trackslistener.h" #include "notificationitem.h" #include "elisaapplication.h" #include "elisa_settings.h" #include "modeldataloader.h" #include #include #include #include #include #include #include #include #include #include #include #include class MusicListenersManagerPrivate { public: QThread mDatabaseThread; QThread mListenerThread; #if defined UPNPQT_FOUND && UPNPQT_FOUND UpnpListener mUpnpListener; #endif #if defined KF5Baloo_FOUND && KF5Baloo_FOUND std::unique_ptr mBalooListener; #endif std::list> mFileListener; +#if defined Qt5AndroidExtras_FOUND && Qt5AndroidExtras_FOUND + std::unique_ptr mAndroidMusicListener; +#endif + DatabaseInterface mDatabaseInterface; std::unique_ptr mTracksListener; QFileSystemWatcher mConfigFileWatcher; ElisaApplication *mElisaApplication = nullptr; int mImportedTracksCount = 0; int mActiveMusicListenersCount = 0; bool mIndexingRunning = false; bool mIndexerBusy = false; }; MusicListenersManager::MusicListenersManager(QObject *parent) : QObject(parent), d(std::make_unique()) { d->mListenerThread.start(); d->mDatabaseThread.start(); d->mDatabaseInterface.moveToThread(&d->mDatabaseThread); connect(&d->mDatabaseInterface, &DatabaseInterface::requestsInitDone, this, &MusicListenersManager::databaseReady); const auto &localDataPaths = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); auto databaseFileName = QString(); if (!localDataPaths.isEmpty()) { QDir myDataDirectory; myDataDirectory.mkpath(localDataPaths.first()); databaseFileName = localDataPaths.first() + QStringLiteral("/elisaDatabase.db"); } QMetaObject::invokeMethod(&d->mDatabaseInterface, "init", Qt::QueuedConnection, Q_ARG(QString, QStringLiteral("listeners")), Q_ARG(QString, databaseFileName)); connect(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()) { auto systemMusicPaths = QStandardPaths::standardLocations(QStandardPaths::MusicLocation); for (const auto &musicPath : qAsConst(systemMusicPaths)) { initialRootPath.push_back(musicPath); } Elisa::ElisaConfiguration::setRootPath(initialRootPath); Elisa::ElisaConfiguration::self()->save(); } d->mConfigFileWatcher.addPath(Elisa::ElisaConfiguration::self()->config()->name()); connect(&d->mDatabaseInterface, &DatabaseInterface::tracksAdded, this, &MusicListenersManager::increaseImportedTracksCount); } MusicListenersManager::~MusicListenersManager() = default; DatabaseInterface *MusicListenersManager::viewDatabase() const { return &d->mDatabaseInterface; } void MusicListenersManager::subscribeForTracks(MediaPlayList *client) { createTracksListener(); connect(d->mTracksListener.get(), &TracksListener::trackHasChanged, client, &MediaPlayList::trackChanged); connect(d->mTracksListener.get(), &TracksListener::trackHasBeenRemoved, client, &MediaPlayList::trackRemoved); connect(d->mTracksListener.get(), &TracksListener::tracksListAdded, client, &MediaPlayList::tracksListAdded); connect(client, &MediaPlayList::newEntryInList, d->mTracksListener.get(), &TracksListener::newEntryInList); connect(client, &MediaPlayList::newTrackByNameInList, d->mTracksListener.get(), &TracksListener::trackByNameInList); } int MusicListenersManager::importedTracksCount() const { return d->mImportedTracksCount; } bool MusicListenersManager::isIndexingRunning() const { return d->mIndexingRunning; } ElisaApplication *MusicListenersManager::elisaApplication() const { return d->mElisaApplication; } bool MusicListenersManager::indexerBusy() const { return d->mIndexerBusy; } void MusicListenersManager::databaseReady() { d->mIndexerBusy = true; Q_EMIT indexerBusyChanged(); configChanged(); } void MusicListenersManager::applicationAboutToQuit() { d->mDatabaseInterface.applicationAboutToQuit(); Q_EMIT applicationIsTerminating(); d->mDatabaseThread.exit(); d->mDatabaseThread.wait(); d->mListenerThread.exit(); d->mListenerThread.wait(); } void MusicListenersManager::showConfiguration() { auto configureAction = d->mElisaApplication->action(QStringLiteral("options_configure")); configureAction->trigger(); } void MusicListenersManager::setElisaApplication(ElisaApplication *elisaApplication) { if (d->mElisaApplication == elisaApplication) { return; } d->mElisaApplication = elisaApplication; emit elisaApplicationChanged(); } void MusicListenersManager::playBackError(const QUrl &sourceInError, QMediaPlayer::Error playerError) { qDebug() << "MusicListenersManager::playBackError" << sourceInError; if (playerError == QMediaPlayer::ResourceError) { Q_EMIT removeTracksInError({sourceInError}); if (sourceInError.isLocalFile()) { Q_EMIT displayTrackError(sourceInError.toLocalFile()); } else { Q_EMIT displayTrackError(sourceInError.toString()); } } } void MusicListenersManager::connectModel(ModelDataLoader *dataLoader) { dataLoader->moveToThread(&d->mDatabaseThread); dataLoader->setDatabase(&d->mDatabaseInterface); } 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 = std::make_unique(); d->mBalooListener->moveToThread(&d->mListenerThread); d->mBalooListener->setDatabaseInterface(&d->mDatabaseInterface); connect(this, &MusicListenersManager::applicationIsTerminating, d->mBalooListener.get(), &BalooListener::applicationAboutToQuit, Qt::DirectConnection); connect(d->mBalooListener.get(), &BalooListener::indexingStarted, this, &MusicListenersManager::monitorStartingListeners); connect(d->mBalooListener.get(), &BalooListener::indexingFinished, this, &MusicListenersManager::monitorEndingListeners); connect(d->mBalooListener.get(), &BalooListener::clearDatabase, &d->mDatabaseInterface, &DatabaseInterface::removeAllTracksFromSource); connect(d->mBalooListener.get(), &BalooListener::newNotification, this, &MusicListenersManager::newNotification); connect(d->mBalooListener.get(), &BalooListener::closeNotification, this, &MusicListenersManager::closeNotification); } else if (!currentConfiguration->balooIndexer() && d->mBalooListener) { QMetaObject::invokeMethod(d->mBalooListener.get(), "quitListener", Qt::QueuedConnection); d->mBalooListener.reset(); } #endif #if defined UPNPQT_FOUND && UPNPQT_FOUND d->mUpnpListener.setDatabaseInterface(&d->mDatabaseInterface); d->mUpnpListener.moveToThread(&d->mDatabaseThread); connect(this, &MusicListenersManager::applicationIsTerminating, &d->mUpnpListener, &UpnpListener::applicationAboutToQuit, Qt::DirectConnection); #endif +#if defined Qt5AndroidExtras_FOUND && Qt5AndroidExtras_FOUND + if (!d->mAndroidMusicListener) { + d->mAndroidMusicListener = std::make_unique(); + d->mAndroidMusicListener->moveToThread(&d->mListenerThread); + d->mAndroidMusicListener->setDatabaseInterface(&d->mDatabaseInterface); + connect(this, &MusicListenersManager::applicationIsTerminating, + d->mAndroidMusicListener.get(), &AndroidMusicListener::applicationAboutToQuit, Qt::DirectConnection); + connect(d->mAndroidMusicListener.get(), &AndroidMusicListener::indexingStarted, + this, &MusicListenersManager::monitorStartingListeners); + connect(d->mAndroidMusicListener.get(), &AndroidMusicListener::indexingFinished, + this, &MusicListenersManager::monitorEndingListeners); + connect(d->mAndroidMusicListener.get(), &AndroidMusicListener::clearDatabase, + &d->mDatabaseInterface, &DatabaseInterface::removeAllTracksFromSource); + connect(d->mAndroidMusicListener.get(), &AndroidMusicListener::newNotification, + this, &MusicListenersManager::newNotification); + connect(d->mAndroidMusicListener.get(), &AndroidMusicListener::closeNotification, + this, &MusicListenersManager::closeNotification); + } +#endif + if (currentConfiguration->elisaFilesIndexer()) { const auto &allRootPaths = currentConfiguration->rootPath(); for (auto itFileListener = d->mFileListener.begin(); itFileListener != d->mFileListener.end(); ) { const auto ¤tRootPath = (*itFileListener)->localFileIndexer().rootPath(); auto itPath = std::find(allRootPaths.begin(), allRootPaths.end(), currentRootPath); if (itPath == allRootPaths.end()) { d->mDatabaseInterface.removeAllTracksFromSource((*itFileListener)->fileListing()->sourceName()); itFileListener = d->mFileListener.erase(itFileListener); } else { ++itFileListener; } } for (const auto &oneRootPath : allRootPaths) { auto itPath = std::find_if(d->mFileListener.begin(), d->mFileListener.end(), [&oneRootPath](const auto &value)->bool {return value->localFileIndexer().rootPath() == oneRootPath;}); if (itPath == d->mFileListener.end()) { auto newFileIndexer = std::make_unique(); newFileIndexer->setDatabaseInterface(&d->mDatabaseInterface); newFileIndexer->moveToThread(&d->mListenerThread); connect(this, &MusicListenersManager::applicationIsTerminating, newFileIndexer.get(), &FileListener::applicationAboutToQuit, Qt::DirectConnection); connect(newFileIndexer.get(), &FileListener::indexingStarted, this, &MusicListenersManager::monitorStartingListeners); connect(newFileIndexer.get(), &FileListener::indexingFinished, this, &MusicListenersManager::monitorEndingListeners); connect(newFileIndexer.get(), &FileListener::newNotification, this, &MusicListenersManager::newNotification); connect(newFileIndexer.get(), &FileListener::closeNotification, this, &MusicListenersManager::closeNotification); newFileIndexer->setRootPath(oneRootPath); d->mFileListener.emplace_back(std::move(newFileIndexer)); } } } } void MusicListenersManager::increaseImportedTracksCount(const DatabaseInterface::ListTrackDataType &allTracks) { d->mImportedTracksCount += allTracks.size(); if (d->mImportedTracksCount && d->mIndexerBusy) { d->mIndexerBusy = false; Q_EMIT indexerBusyChanged(); } //if (d->mImportedTracksCount >= 4) { Q_EMIT closeNotification(QStringLiteral("notEnoughTracks")); //} Q_EMIT importedTracksCountChanged(); } void MusicListenersManager::decreaseImportedTracksCount() { --d->mImportedTracksCount; Q_EMIT importedTracksCountChanged(); } void MusicListenersManager::monitorStartingListeners() { if (d->mActiveMusicListenersCount == 0) { d->mIndexingRunning = true; Q_EMIT indexingRunningChanged(); } ++d->mActiveMusicListenersCount; } void MusicListenersManager::monitorEndingListeners() { --d->mActiveMusicListenersCount; if (d->mActiveMusicListenersCount == 0) { /*if (d->mImportedTracksCount < 4 && d->mElisaApplication) { NotificationItem notEnoughTracks; notEnoughTracks.setNotificationId(QStringLiteral("notEnoughTracks")); notEnoughTracks.setTargetObject(this); notEnoughTracks.setMessage(i18nc("No track found message", "No track have been found")); auto configureAction = d->mElisaApplication->action(QStringLiteral("options_configure")); notEnoughTracks.setMainButtonText(configureAction->text()); notEnoughTracks.setMainButtonIconName(configureAction->icon().name()); notEnoughTracks.setMainButtonMethodName(QStringLiteral("showConfiguration")); Q_EMIT newNotification(notEnoughTracks); }*/ d->mIndexingRunning = false; Q_EMIT indexingRunningChanged(); } } void MusicListenersManager::createTracksListener() { if (!d->mTracksListener) { d->mTracksListener = std::make_unique(&d->mDatabaseInterface); d->mTracksListener->moveToThread(&d->mDatabaseThread); connect(this, &MusicListenersManager::removeTracksInError, &d->mDatabaseInterface, &DatabaseInterface::removeTracksList); connect(&d->mDatabaseInterface, &DatabaseInterface::trackRemoved, d->mTracksListener.get(), &TracksListener::trackRemoved); connect(&d->mDatabaseInterface, &DatabaseInterface::tracksAdded, d->mTracksListener.get(), &TracksListener::tracksAdded); connect(&d->mDatabaseInterface, &DatabaseInterface::trackModified, d->mTracksListener.get(), &TracksListener::trackModified); } } #include "moc_musiclistenersmanager.cpp" diff --git a/src/resources.qrc b/src/resources.qrc index 0345c99e..f34715e7 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -1,55 +1,59 @@ qml/MediaPlayerControl.qml qml/RatingStar.qml qml/MediaPlayListView.qml qml/ElisaMainWindow.qml qml/ApplicationMenu.qml qml/HeaderBar.qml qml/ContextView.qml qml/ContentView.qml qml/DraggableItem.qml qml/PassiveNotification.qml qml/NavigationActionBar.qml qml/PlayListEntry.qml qml/MediaBrowser.qml qml/Theme.qml qml/PlatformIntegration.qml qml/LabelWithToolTip.qml qml/TopNotification.qml qml/TrackImportNotification.qml qml/TopNotificationItem.qml qml/ViewSelector.qml qml/MediaTrackDelegate.qml qml/MediaAlbumTrackDelegate.qml qml/MediaTrackMetadataView.qml qml/GridBrowserView.qml qml/GridBrowserDelegate.qml qml/ListBrowserView.qml qml/FileBrowserDelegate.qml qml/FileBrowserView.qml qtquickcontrols2.conf background.png qml/ViewManager.qml qml/BaseTheme.qml qml/ScrollHelper.qml qml/FlatButtonWithToolTip.qml qml/AlbumsView.qml qml/ArtistsView.qml qml/GenresView.qml qml/TracksView.qml qml/AlbumView.qml qml/OneArtistView.qml qml/OneGenreView.qml windows/WindowsTheme.qml windows/PlatformIntegration.qml windows/LabelWithToolTip.qml android/ElisaMainWindow.qml android/AndroidTheme.qml android/PlatformIntegration.qml + android/AlbumsView.qml + android/ArtistsView.qml + android/TracksView.qml + android/GenresView.qml