Commit f47e0047 authored by Matthieu Gallien's avatar Matthieu Gallien 🎵

introduce elisa_local_file kcm module to configure path for elisa music indexer

allow to configure the paths searched by elisa local music files indexer
parent c38e9767
......@@ -27,10 +27,10 @@ find_package(KF5Baloo ${REQUIRED_KF5_VERSION} CONFIG QUIET)
find_package(KF5FileMetaData ${REQUIRED_KF5_VERSION} CONFIG QUIET)
find_package(KF5DocTools ${REQUIRED_KF5_VERSION} CONFIG QUIET)
find_package(KF5XmlGui ${REQUIRED_KF5_VERSION} CONFIG QUIET)
find_package(KF5ConfigWidgets ${REQUIRED_KF5_VERSION} CONFIG QUIET)
find_package(KF5Config ${REQUIRED_KF5_VERSION} CONFIG QUIET)
find_package(KF5Config ${REQUIRED_KF5_VERSION} CONFIG REQUIRED QUIET)
find_package(KF5Crash ${REQUIRED_KF5_VERSION} CONFIG QUIET)
find_package(KF5DBusAddons ${REQUIRED_KF5_VERSION} CONFIG QUIET)
find_package(KF5KCMUtils ${REQUIRED_KF5_VERSION} CONFIG REQUIRED QUIET)
find_package(UPNPQT CONFIG QUIET)
set_package_properties(UPNPQT PROPERTIES
......
enable_testing()
include_directories(${elisa_BINARY_DIR})
include_directories(${elisa_BINARY_DIR}/src)
set(databaseInterfaceTest_SOURCES
../src/databaseinterface.cpp
......@@ -60,9 +61,15 @@ if (KF5FileMetaData_FOUND)
)
endif()
kconfig_add_kcfg_files(playListControlerTest_SOURCES ../src/elisa_settings.kcfgc )
set(playListControlerTest_SOURCES
${playListControlerTest_SOURCES}
../src/elisa_core.kcfg
)
add_executable(playListControlerTest ${playListControlerTest_SOURCES})
target_link_libraries(playListControlerTest Qt5::Test Qt5::Core Qt5::Sql KF5::I18n)
target_link_libraries(playListControlerTest Qt5::Test Qt5::Core Qt5::Sql KF5::I18n KF5::ConfigCore KF5::ConfigGui)
if (KF5Baloo_FOUND)
target_link_libraries(playListControlerTest KF5::Baloo Qt5::DBus)
endif()
......@@ -121,9 +128,15 @@ if (KF5FileMetaData_FOUND)
)
endif()
kconfig_add_kcfg_files(managemediaplayercontrolTest_SOURCES ../src/elisa_settings.kcfgc )
set(managemediaplayercontrolTest_SOURCES
${managemediaplayercontrolTest_SOURCES}
../src/elisa_core.kcfg
)
add_executable(managemediaplayercontrolTest ${managemediaplayercontrolTest_SOURCES})
target_link_libraries(managemediaplayercontrolTest Qt5::Test Qt5::Core Qt5::Sql KF5::I18n)
target_link_libraries(managemediaplayercontrolTest Qt5::Test Qt5::Core Qt5::Sql KF5::I18n KF5::ConfigCore KF5::ConfigGui)
if (KF5Baloo_FOUND)
target_link_libraries(managemediaplayercontrolTest KF5::Baloo Qt5::DBus)
endif()
......@@ -183,9 +196,15 @@ if (KF5FileMetaData_FOUND)
)
endif()
kconfig_add_kcfg_files(manageheaderbarTest_SOURCES ../src/elisa_settings.kcfgc )
set(manageheaderbarTest_SOURCES
${manageheaderbarTest_SOURCES}
../src/elisa_core.kcfg
)
add_executable(manageheaderbarTest ${manageheaderbarTest_SOURCES})
target_link_libraries(manageheaderbarTest Qt5::Test Qt5::Core Qt5::Sql Qt5::Gui KF5::I18n)
target_link_libraries(manageheaderbarTest Qt5::Test Qt5::Core Qt5::Sql Qt5::Gui KF5::I18n KF5::ConfigCore KF5::ConfigGui)
if (KF5Baloo_FOUND)
target_link_libraries(manageheaderbarTest KF5::Baloo Qt5::DBus)
endif()
......@@ -253,9 +272,15 @@ if (KF5FileMetaData_FOUND)
)
endif()
kconfig_add_kcfg_files(mediaplaylistTest_SOURCES ../src/elisa_settings.kcfgc )
set(mediaplaylistTest_SOURCES
${mediaplaylistTest_SOURCES}
../src/elisa_core.kcfg
)
add_executable(mediaplaylistTest ${mediaplaylistTest_SOURCES})
target_link_libraries(mediaplaylistTest Qt5::Test Qt5::Core Qt5::Sql Qt5::Gui KF5::I18n)
target_link_libraries(mediaplaylistTest Qt5::Test Qt5::Core Qt5::Sql Qt5::Gui KF5::I18n KF5::ConfigCore KF5::ConfigGui)
if (KF5Baloo_FOUND)
target_link_libraries(mediaplaylistTest KF5::Baloo Qt5::DBus)
endif()
......@@ -333,11 +358,16 @@ if (KF5FileMetaData_FOUND)
localfilelistingtest.cpp
)
kconfig_add_kcfg_files(localfilelistingtest_SOURCES ../src/elisa_settings.kcfgc )
set(localfilelistingtest_SOURCES
${localfilelistingtest_SOURCES}
../src/elisa_core.kcfg
)
add_executable(localfilelistingtest ${localfilelistingtest_SOURCES})
target_link_libraries(localfilelistingtest Qt5::Test Qt5::Core Qt5::Sql KF5::I18n)
if (KF5FileMetaData_FOUND)
target_link_libraries(localfilelistingtest KF5::FileMetaData)
endif()
target_link_libraries(localfilelistingtest Qt5::Test Qt5::Core Qt5::Sql KF5::I18n KF5::ConfigCore KF5::ConfigGui KF5::FileMetaData)
target_include_directories(localfilelistingtest PRIVATE ${CMAKE_SOURCE_DIR}/src)
add_test(localfilelistingtest localfilelistingtest)
endif()
......@@ -8,10 +8,6 @@
#cmakedefine01 KF5XmlGui_FOUND
#cmakedefine01 KF5ConfigWidgets_FOUND
#cmakedefine01 KF5Config_FOUND
#cmakedefine01 KF5Crash_FOUND
#cmakedefine01 KF5FileMetaData_FOUND
......
......@@ -101,6 +101,12 @@ if (Qt5Quick_FOUND AND Qt5Widgets_FOUND)
)
endif()
kconfig_add_kcfg_files(elisa_SOURCES elisa_settings.kcfgc)
set(elisa_SOURCES
${elisa_SOURCES}
elisa_core.kcfg
)
qt5_add_resources(elisa_SOURCES upnpControl.qrc)
add_executable(elisa ${elisa_SOURCES})
......@@ -119,6 +125,8 @@ if (Qt5Quick_FOUND AND Qt5Widgets_FOUND)
Qt5::Xml
Qt5::Sql
KF5::I18n
KF5::ConfigCore
KF5::KCMUtils
)
if (Qt5DBus_FOUND)
......@@ -170,20 +178,6 @@ if (Qt5Quick_FOUND AND Qt5Widgets_FOUND)
)
endif()
if (KF5ConfigWidgets_FOUND)
target_link_libraries(elisa
LINK_PRIVATE
KF5::ConfigWidgets
)
endif()
if (KF5Config_FOUND)
target_link_libraries(elisa
LINK_PRIVATE
KF5::ConfigCore
)
endif()
if (KF5Crash_FOUND)
target_link_libraries(elisa
LINK_PRIVATE
......@@ -208,3 +202,5 @@ if (Qt5Quick_FOUND AND Qt5Widgets_FOUND)
endif()
install(TARGETS elisa ${INSTALL_TARGETS_DEFAULT_ARGS})
add_subdirectory(localFileConfiguration)
......@@ -46,6 +46,7 @@ ApplicationWindow {
property var reportBugAction: elisa.action("help_report_bug")
property var aboutAppAction: elisa.action("help_about_app")
property var configureShortcutsAction: elisa.action("options_configure_keybinding")
property var configureAction: elisa.action("options_configure")
SystemPalette {
id: myPalette
......@@ -327,6 +328,14 @@ ApplicationWindow {
visible: reportBugAction.text !== ""
}
MenuItem {
text: configureAction.text
shortcut: configureAction.shortcut
iconName: 'configure'
onTriggered: configureAction.trigger()
visible: configureAction.text !== ""
}
MenuItem {
text: configureShortcutsAction.text
shortcut: configureShortcutsAction.shortcut
......@@ -336,7 +345,7 @@ ApplicationWindow {
}
MenuSeparator {
visible: configureShortcutsAction.text !== ""
visible: configureAction.text !== "" || configureShortcutsAction.text !== ""
}
MenuItem {
......
......@@ -28,25 +28,23 @@ class AbstractFileListenerPrivate
{
public:
explicit AbstractFileListenerPrivate(AbstractFileListing *aFileListing) : mFileListing(aFileListing)
explicit AbstractFileListenerPrivate()
{
}
QThread mFileQueryThread;
AbstractFileListing *mFileListing;
AbstractFileListing *mFileListing = nullptr;
};
AbstractFileListener::AbstractFileListener(AbstractFileListing *aFileListing, QObject *parent) : QObject(parent), d(new AbstractFileListenerPrivate(aFileListing))
AbstractFileListener::AbstractFileListener(QObject *parent)
: QObject(parent), d(new AbstractFileListenerPrivate)
{
d->mFileQueryThread.start();
d->mFileListing->moveToThread(&d->mFileQueryThread);
}
AbstractFileListener::~AbstractFileListener()
{
delete d->mFileListing;
delete d;
}
......@@ -78,10 +76,24 @@ void AbstractFileListener::applicationAboutToQuit()
d->mFileQueryThread.wait();
}
void AbstractFileListener::setFileListing(AbstractFileListing *fileIndexer)
{
d->mFileListing = fileIndexer;
d->mFileQueryThread.start();
d->mFileListing->moveToThread(&d->mFileQueryThread);
connect(fileIndexer, &AbstractFileListing::indexingFinished,
this, &AbstractFileListener::indexingFinished);
}
AbstractFileListing *AbstractFileListener::fileListing() const
{
return d->mFileListing;
}
void AbstractFileListener::performInitialScan()
{
d->mFileListing->refreshContent();
}
#include "moc_abstractfilelistener.cpp"
......@@ -39,12 +39,14 @@ class AbstractFileListener : public QObject
NOTIFY databaseInterfaceChanged)
public:
explicit AbstractFileListener(AbstractFileListing *aFileListing, QObject *parent = 0);
explicit AbstractFileListener(QObject *parent = 0);
virtual ~AbstractFileListener();
DatabaseInterface* databaseInterface() const;
AbstractFileListing* fileListing() const;
Q_SIGNALS:
void databaseInterfaceChanged();
......@@ -53,15 +55,21 @@ Q_SIGNALS:
void newTrackFile(const MusicAudioTrack &newTrack);
void indexingFinished();
void configurationChanged();
public Q_SLOTS:
void performInitialScan();
void setDatabaseInterface(DatabaseInterface* databaseInterface);
void applicationAboutToQuit();
protected:
AbstractFileListing* fileListing() const;
void setFileListing(AbstractFileListing *fileIndexer);
private:
......
......@@ -428,5 +428,10 @@ void AbstractFileListing::removeFile(const QUrl &oneRemovedTrack, QList<QUrl> &a
}
}
void AbstractFileListing::setSourceName(const QString &name)
{
d->mSourceName = name;
}
#include "moc_abstractfilelisting.cpp"
......@@ -44,6 +44,8 @@ public:
virtual void applicationAboutToQuit();
const QString &sourceName() const;
Q_SIGNALS:
void tracksList(const QList<MusicAudioTrack> &tracks, const QHash<QString, QUrl> &covers, const QString &musicSource);
......@@ -52,6 +54,8 @@ Q_SIGNALS:
void modifyTracksList(const QList<MusicAudioTrack> &modifiedTracks, const QHash<QString, QUrl> &covers);
void indexingFinished();
public Q_SLOTS:
void refreshContent();
......@@ -76,8 +80,6 @@ protected:
void scanDirectory(QList<MusicAudioTrack> &newFiles, const QUrl &path);
const QString &sourceName() const;
virtual MusicAudioTrack scanOneFile(const QUrl &scanFile);
void watchPath(const QString &pathName);
......@@ -96,6 +98,8 @@ protected:
void removeFile(const QUrl &oneRemovedTrack, QList<QUrl> &allRemovedFiles);
void setSourceName(const QString &name);
private:
std::unique_ptr<AbstractFileListingPrivate> d;
......
......@@ -51,6 +51,11 @@ AllAlbumsModel::~AllAlbumsModel()
delete d;
}
int AllAlbumsModel::albumCount() const
{
return rowCount({});
}
int AllAlbumsModel::rowCount(const QModelIndex &parent) const
{
auto albumCount = 0;
......
......@@ -58,6 +58,8 @@ public:
virtual ~AllAlbumsModel();
Q_INVOKABLE int albumCount() const;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QHash<int, QByteArray> roleNames() const override;
......
......@@ -28,10 +28,13 @@ class BalooListenerPrivate
{
public:
LocalBalooFileListing mBalooFileIndexer;
};
BalooListener::BalooListener(QObject *parent) : AbstractFileListener(new LocalBalooFileListing, parent), d(new BalooListenerPrivate)
BalooListener::BalooListener(QObject *parent) : AbstractFileListener(parent), d(new BalooListenerPrivate)
{
setFileListing(&d->mBalooFileIndexer);
}
BalooListener::~BalooListener()
......
......@@ -126,6 +126,8 @@ void LocalBalooFileListing::triggerRefreshOfContent()
if (!newFiles.isEmpty() && d->mStopRequest == 0) {
emitNewFiles(newFiles);
}
Q_EMIT indexingFinished();
}
MusicAudioTrack LocalBalooFileListing::scanOneFile(const QUrl &scanFile)
......
......@@ -57,7 +57,8 @@ public:
mInitialUpdateTracksValidity(mTracksDatabase), mUpdateTrackMapping(mTracksDatabase),
mSelectTracksMapping(mTracksDatabase), mSelectTracksMappingPriority(mTracksDatabase),
mUpdateAlbumArtUriFromAlbumIdQuery(mTracksDatabase), mUpdateAlbumArtistFromAlbumIdQuery(mTracksDatabase),
mInsertAlbumWithoutArtistQuery(mTracksDatabase), mSelectTracksMappingPriorityByTrackId(mTracksDatabase)
mInsertAlbumWithoutArtistQuery(mTracksDatabase), mSelectTracksMappingPriorityByTrackId(mTracksDatabase),
mSelectAllTrackFilesFromSourceQuery(mTracksDatabase)
{
}
......@@ -137,6 +138,8 @@ public:
QSqlQuery mSelectTracksMappingPriorityByTrackId;
QSqlQuery mSelectAllTrackFilesFromSourceQuery;
qulonglong mAlbumId = 1;
qulonglong mArtistId = 1;
......@@ -273,6 +276,13 @@ QList<MusicAudioTrack> DatabaseInterface::allTracksFromSource(const QString &mus
qDebug() << "DatabaseInterface::allAlbums" << d->mSelectAllTracksFromSourceQuery.boundValues();
qDebug() << "DatabaseInterface::allAlbums" << d->mSelectAllTracksFromSourceQuery.lastError();
d->mSelectAllTracksFromSourceQuery.finish();
transactionResult = finishTransaction();
if (!transactionResult) {
return result;
}
return result;
}
......@@ -586,6 +596,81 @@ void DatabaseInterface::applicationAboutToQuit()
d->mStopRequest = 1;
}
void DatabaseInterface::removeAllTracksFromSource(const QString &sourceName)
{
auto transactionResult = startTransaction();
if (!transactionResult) {
return;
}
d->mSelectMusicSource.bindValue(QStringLiteral(":name"), sourceName);
auto queryResult = d->mSelectMusicSource.exec();
if (!queryResult || !d->mSelectMusicSource.isSelect() || !d->mSelectMusicSource.isActive()) {
qDebug() << "DatabaseInterface::insertMusicSource" << d->mSelectMusicSource.lastQuery();
qDebug() << "DatabaseInterface::insertMusicSource" << d->mSelectMusicSource.boundValues();
qDebug() << "DatabaseInterface::insertMusicSource" << d->mSelectMusicSource.lastError();
d->mSelectMusicSource.finish();
transactionResult = finishTransaction();
if (!transactionResult) {
return;
}
return;
}
if (!d->mSelectMusicSource.next()) {
transactionResult = finishTransaction();
if (!transactionResult) {
return;
}
return;
}
qulonglong sourceId = d->mSelectMusicSource.record().value(0).toULongLong();
d->mSelectMusicSource.finish();
d->mSelectAllTrackFilesFromSourceQuery.bindValue(QStringLiteral(":discoverId"), sourceId);
queryResult = d->mSelectAllTrackFilesFromSourceQuery.exec();
if (!queryResult || !d->mSelectAllTrackFilesFromSourceQuery.isSelect() || !d->mSelectAllTrackFilesFromSourceQuery.isActive()) {
qDebug() << "DatabaseInterface::insertMusicSource" << d->mSelectAllTrackFilesFromSourceQuery.lastQuery();
qDebug() << "DatabaseInterface::insertMusicSource" << d->mSelectAllTrackFilesFromSourceQuery.boundValues();
qDebug() << "DatabaseInterface::insertMusicSource" << d->mSelectAllTrackFilesFromSourceQuery.lastError();
d->mSelectAllTrackFilesFromSourceQuery.finish();
transactionResult = finishTransaction();
if (!transactionResult) {
return;
}
return;
}
QList<QUrl> allFileNames;
while(d->mSelectAllTrackFilesFromSourceQuery.next()) {
auto fileName = d->mSelectAllTrackFilesFromSourceQuery.record().value(0).toUrl();
allFileNames.push_back(fileName);
}
d->mSelectAllTrackFilesFromSourceQuery.finish();
internalRemoveTracksList(allFileNames);
transactionResult = finishTransaction();
if (!transactionResult) {
return;
}
}
void DatabaseInterface::insertTracksList(const QList<MusicAudioTrack> &tracks, const QHash<QString, QUrl> &covers, const QString &musicSource)
{
if (d->mStopRequest == 1) {
......@@ -668,62 +753,7 @@ void DatabaseInterface::removeTracksList(const QList<QUrl> &removedTracks)
return;
}
QList<MusicAudioTrack> willRemoveTrack;
for (const auto &removedTrackFileName : removedTracks) {
d->mSelectTrackFromFilePathQuery.bindValue(QStringLiteral(":filePath"), removedTrackFileName.toString());
auto result = d->mSelectTrackFromFilePathQuery.exec();
if (!result || !d->mSelectTrackFromFilePathQuery.isSelect() || !d->mSelectTrackFromFilePathQuery.isActive()) {
qDebug() << "DatabaseInterface::removeTracksList" << d->mSelectTrackFromFilePathQuery.lastQuery();
qDebug() << "DatabaseInterface::removeTracksList" << d->mSelectTrackFromFilePathQuery.boundValues();
qDebug() << "DatabaseInterface::removeTracksList" << d->mSelectTrackFromFilePathQuery.lastError();
continue;
}
while (d->mSelectTrackFromFilePathQuery.next()) {
const auto &currentRecord = d->mSelectTrackFromFilePathQuery.record();
willRemoveTrack.push_back(buildTrackFromDatabaseRecord(currentRecord));
}
d->mSelectTrackFromFilePathQuery.finish();
}
QSet<qulonglong> modifiedAlbums;
for (const auto &oneRemovedTrack : willRemoveTrack) {
removeTrackInDatabase(oneRemovedTrack.databaseId());
Q_EMIT trackRemoved(oneRemovedTrack.databaseId());
const auto &modifiedAlbumId = internalAlbumIdFromTitle(oneRemovedTrack.albumName());
const auto &allArtistTracks = internalTracksFromAuthor(oneRemovedTrack.artist());
const auto &removedArtistId = internalArtistIdFromName(oneRemovedTrack.artist());
const auto &removedArtist = internalArtistFromId(removedArtistId);
if (updateTracksCount(modifiedAlbumId)) {
modifiedAlbums.insert(modifiedAlbumId);
}
updateAlbumFromId(modifiedAlbumId, oneRemovedTrack.albumCover(), oneRemovedTrack);
if (allArtistTracks.isEmpty()) {
removeArtistInDatabase(removedArtistId);
Q_EMIT artistRemoved(removedArtist);
}
}
for (auto modifiedAlbumId : modifiedAlbums) {
auto modifiedAlbum = internalAlbumFromId(modifiedAlbumId);
if (modifiedAlbum.isValid() && !modifiedAlbum.isEmpty()) {
Q_EMIT albumModified(modifiedAlbum, modifiedAlbumId);
} else {
removeAlbumInDatabase(modifiedAlbum.databaseId());
Q_EMIT albumRemoved(modifiedAlbum, modifiedAlbumId);
}
}
internalRemoveTracksList(removedTracks);
transactionResult = finishTransaction();
if (!transactionResult) {
......@@ -1399,6 +1429,21 @@ void DatabaseInterface::initRequest()
}
}
{
auto selectAllTrackFilesFromSourceQueryText = QStringLiteral("SELECT "
"tracksMapping.`FileName` "
"FROM "
"`TracksMapping` tracksMapping "
"WHERE "
"tracksMapping.`DiscoverID` = :discoverId");
auto result = d->mSelectAllTrackFilesFromSourceQuery.prepare(selectAllTrackFilesFromSourceQueryText);
if (!result) {
qDebug() << "DatabaseInterface::initRequest" << d->mSelectAllTrackFilesFromSourceQuery.lastError();
}
}
{
auto insertMusicSourceQueryText = QStringLiteral("INSERT OR IGNORE INTO `DiscoverSource` (`ID`, `Name`) "
"VALUES (:discoverId, :name)");
......@@ -2052,6 +2097,68 @@ MusicAudioTrack DatabaseInterface::buildTrackFromDatabaseRecord(const QSqlRecord
return result;
}
void DatabaseInterface::internalRemoveTracksList(const QList<QUrl> &removedTracks)
{
QList<MusicAudioTrack> willRemoveTrack;
qDebug() << "DatabaseInterface::internalRemoveTracksList" << removedTracks;
for (const auto &removedTrackFileName : removedTracks) {
d->mSelectTrackFromFilePathQuery.bindValue(QStringLiteral(":filePath"), removedTrackFileName.toString());
auto result = d->mSelectTrackFromFilePathQuery.exec();
if (!result || !d->mSelectTrackFromFilePathQuery.isSelect() || !d->mSelectTrackFromFilePathQuery.isActive()) {
qDebug() << "DatabaseInterface::removeTracksList" << d->mSelectTrackFromFilePathQuery.lastQuery();
qDebug() << "DatabaseInterface::removeTracksList" << d->mSelectTrackFromFilePathQuery.boundValues();
qDebug() << "DatabaseInterface::removeTracksList" << d->mSelectTrackFromFilePathQuery.lastError();
continue;
}
while (d->mSelectTrackFromFilePathQuery.next()) {
const auto &currentRecord = d->mSelectTrackFromFilePathQuery.record();
willRemoveTrack.push_back(buildTrackFromDatabaseRecord(currentRecord));
}
d->mSelectTrackFromFilePathQuery.finish();
}
QSet<qulonglong> modifiedAlbums;
for (const auto &oneRemovedTrack : willRemoveTrack) {
removeTrackInDatabase(oneRemovedTrack.databaseId());
Q_EMIT trackRemoved(oneRemovedTrack.databaseId());
const auto &modifiedAlbumId = internalAlbumIdFromTitle(oneRemovedTrack.albumName());
const auto &allArtistTracks = internalTracksFromAuthor(oneRemovedTrack.artist());
const auto &removedArtistId = internalArtistIdFromName(oneRemovedTrack.artist());
const auto &removedArtist = internalArtistFromId(removedArtistId);
if (updateTracksCount(modifiedAlbumId)) {
modifiedAlbums.insert(modifiedAlbumId);
}
updateAlbumFromId(modifiedAlbumId, oneRemovedTrack.albumCover(), oneRemovedTrack);
if (allArtistTracks.isEmpty()) {
removeArtistInDatabase(removedArtistId);
Q_EMIT artistRemoved(removedArtist);
}
}
for (auto modifiedAlbumId : modifiedAlbums) {
auto modifiedAlbum = internalAlbumFromId(modifiedAlbumId);