[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-commits
Subject:    [elisa] /: introduce elisa_local_file kcm module to configure path for elisa music indexer
From:       Matthieu Gallien <null () kde ! org>
Date:       2017-07-31 21:34:17
Message-ID: E1dcIKP-0005Nl-8y () code ! kde ! org
[Download RAW message or body]

Git commit f47e0047fde2ee1cbcd34a24547d7ab70e67264a by Matthieu Gallien.
Committed on 31/07/2017 at 21:33.
Pushed by mgallien into branch 'master'.

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

M  +2    -2    CMakeLists.txt
M  +38   -8    autotests/CMakeLists.txt
M  +0    -4    config-upnp-qt.h.cmake
M  +10   -14   src/CMakeLists.txt
M  +10   -1    src/MediaServer.qml
M  +18   -6    src/abstractfile/abstractfilelistener.cpp
M  +10   -2    src/abstractfile/abstractfilelistener.h
M  +5    -0    src/abstractfile/abstractfilelisting.cpp
M  +6    -2    src/abstractfile/abstractfilelisting.h
M  +5    -0    src/allalbumsmodel.cpp
M  +2    -0    src/allalbumsmodel.h
M  +4    -1    src/baloo/baloolistener.cpp
M  +2    -0    src/baloo/localbaloofilelisting.cpp
M  +164  -57   src/databaseinterface.cpp
M  +4    -0    src/databaseinterface.h
M  +19   -7    src/elisaapplication.cpp
M  +6    -0    src/elisaapplication.h
M  +14   -1    src/file/filelistener.cpp
M  +5    -2    src/file/filelistener.h
M  +8    -7    src/file/localfilelisting.cpp
M  +2    -0    src/file/localfilelisting.h
A  +31   -0    src/localFileConfiguration/CMakeLists.txt
A  +13   -0    src/localFileConfiguration/kcm_elisa_local_file.desktop
A  +117  -0    src/localFileConfiguration/localfileconfiguration.cpp     [License: \
LGPL (v3+)] C  +23   -22   src/localFileConfiguration/localfileconfiguration.h [from: \
src/file/localfilelisting.h - 057% similarity] A  +154  -0    \
src/localFileConfiguration/package/contents/ui/main.qml     [License: LGPL (v3+)] A  \
+16   -0    src/localFileConfiguration/package/metadata.desktop M  +108  -28   \
src/musiclistenersmanager.cpp M  +8    -0    src/musiclistenersmanager.h
M  +7    -0    src/upnpControl.cpp

https://commits.kde.org/elisa/f47e0047fde2ee1cbcd34a24547d7ab70e67264a

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 523ff0c..c8ce6d6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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
diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt
index 453c6cb..d41e359 100644
--- a/autotests/CMakeLists.txt
+++ b/autotests/CMakeLists.txt
@@ -1,6 +1,7 @@
 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()
diff --git a/config-upnp-qt.h.cmake b/config-upnp-qt.h.cmake
index 6628774..80316c5 100644
--- a/config-upnp-qt.h.cmake
+++ b/config-upnp-qt.h.cmake
@@ -8,10 +8,6 @@
 
 #cmakedefine01 KF5XmlGui_FOUND
 
-#cmakedefine01 KF5ConfigWidgets_FOUND
-
-#cmakedefine01 KF5Config_FOUND
-
 #cmakedefine01 KF5Crash_FOUND
 
 #cmakedefine01 KF5FileMetaData_FOUND
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 6f21bfd..aec7d54 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -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)
diff --git a/src/MediaServer.qml b/src/MediaServer.qml
index b7caf65..7eb98f0 100644
--- a/src/MediaServer.qml
+++ b/src/MediaServer.qml
@@ -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
@@ -328,6 +329,14 @@ ApplicationWindow {
         }
 
         MenuItem {
+            text: configureAction.text
+            shortcut: configureAction.shortcut
+            iconName: 'configure'
+            onTriggered: configureAction.trigger()
+            visible: configureAction.text !== ""
+        }
+
+        MenuItem {
             text: configureShortcutsAction.text
             shortcut: configureShortcutsAction.shortcut
             iconName: elisa.iconName(configureShortcutsAction.icon)
@@ -336,7 +345,7 @@ ApplicationWindow {
         }
 
         MenuSeparator {
-            visible: configureShortcutsAction.text !== ""
+            visible: configureAction.text !== "" || configureShortcutsAction.text \
!== ""  }
 
         MenuItem {
diff --git a/src/abstractfile/abstractfilelistener.cpp \
b/src/abstractfile/abstractfilelistener.cpp index a4cc4b5..132c5ef 100644
--- a/src/abstractfile/abstractfilelistener.cpp
+++ b/src/abstractfile/abstractfilelistener.cpp
@@ -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"
diff --git a/src/abstractfile/abstractfilelistener.h \
b/src/abstractfile/abstractfilelistener.h index d033084..aa34a24 100644
--- a/src/abstractfile/abstractfilelistener.h
+++ b/src/abstractfile/abstractfilelistener.h
@@ -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:
 
diff --git a/src/abstractfile/abstractfilelisting.cpp \
b/src/abstractfile/abstractfilelisting.cpp index e0ee14f..d768edc 100644
--- a/src/abstractfile/abstractfilelisting.cpp
+++ b/src/abstractfile/abstractfilelisting.cpp
@@ -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"
diff --git a/src/abstractfile/abstractfilelisting.h \
b/src/abstractfile/abstractfilelisting.h index 9ba4344..4b2561a 100644
--- a/src/abstractfile/abstractfilelisting.h
+++ b/src/abstractfile/abstractfilelisting.h
@@ -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;
diff --git a/src/allalbumsmodel.cpp b/src/allalbumsmodel.cpp
index 39e4fbc..fc28181 100644
--- a/src/allalbumsmodel.cpp
+++ b/src/allalbumsmodel.cpp
@@ -51,6 +51,11 @@ AllAlbumsModel::~AllAlbumsModel()
     delete d;
 }
 
+int AllAlbumsModel::albumCount() const
+{
+    return rowCount({});
+}
+
 int AllAlbumsModel::rowCount(const QModelIndex &parent) const
 {
     auto albumCount = 0;
diff --git a/src/allalbumsmodel.h b/src/allalbumsmodel.h
index 62f2a42..c29e13b 100644
--- a/src/allalbumsmodel.h
+++ b/src/allalbumsmodel.h
@@ -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;
diff --git a/src/baloo/baloolistener.cpp b/src/baloo/baloolistener.cpp
index be8d878..4d54bdd 100644
--- a/src/baloo/baloolistener.cpp
+++ b/src/baloo/baloolistener.cpp
@@ -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()
diff --git a/src/baloo/localbaloofilelisting.cpp \
b/src/baloo/localbaloofilelisting.cpp index 9288238..a93d429 100644
--- a/src/baloo/localbaloofilelisting.cpp
+++ b/src/baloo/localbaloofilelisting.cpp
@@ -126,6 +126,8 @@ void LocalBalooFileListing::triggerRefreshOfContent()
     if (!newFiles.isEmpty() && d->mStopRequest == 0) {
         emitNewFiles(newFiles);
     }
+
+    Q_EMIT indexingFinished();
 }
 
 MusicAudioTrack LocalBalooFileListing::scanOneFile(const QUrl &scanFile)
diff --git a/src/databaseinterface.cpp b/src/databaseinterface.cpp
index ab19e50..45f738f 100644
--- a/src/databaseinterface.cpp
+++ b/src/databaseinterface.cpp
@@ -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) {
@@ -1400,6 +1430,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);
+
+        if (modifiedAlbum.isValid() && !modifiedAlbum.isEmpty()) {
+            Q_EMIT albumModified(modifiedAlbum, modifiedAlbumId);
+        } else {
+            removeAlbumInDatabase(modifiedAlbum.databaseId());
+            Q_EMIT albumRemoved(modifiedAlbum, modifiedAlbumId);
+        }
+    }
+}
+
 qulonglong DatabaseInterface::internalArtistIdFromName(const QString &name)
 {
     auto result = qulonglong(0);
diff --git a/src/databaseinterface.h b/src/databaseinterface.h
index cbc7947..0498a83 100644
--- a/src/databaseinterface.h
+++ b/src/databaseinterface.h
@@ -75,6 +75,8 @@ public:
 
     void applicationAboutToQuit();
 
+    void removeAllTracksFromSource(const QString &sourceName);
+
 Q_SIGNALS:
 
     void artistAdded(const MusicArtist &newArtist);
@@ -169,6 +171,8 @@ private:
 
     MusicAudioTrack buildTrackFromDatabaseRecord(const QSqlRecord &trackRecord) \
const;  
+    void internalRemoveTracksList(const QList<QUrl> &removedTracks);
+
     DatabaseInterfacePrivate *d;
 
 };
diff --git a/src/elisaapplication.cpp b/src/elisaapplication.cpp
index 1725c5e..9f06881 100644
--- a/src/elisaapplication.cpp
+++ b/src/elisaapplication.cpp
@@ -20,6 +20,8 @@
 
 #include "elisaapplication.h"
 
+#include "elisa_settings.h"
+
 #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND
 #include <KXmlGui/KAboutApplicationDialog>
 #include <KXmlGui/KHelpMenu>
@@ -27,13 +29,8 @@
 #include <KXmlGui/KShortcutsDialog>
 #endif
 
-#if defined KF5ConfigWidgets_FOUND && KF5ConfigWidgets_FOUND
 #include <KConfigWidgets/KStandardAction>
-#endif
-
-#if defined KF5Config_FOUND && KF5Config_FOUND
 #include <KConfigCore/KAuthorized>
-#endif
 
 #if defined KF5CoreAddons_FOUND && KF5CoreAddons_FOUND
 #include <KCoreAddons/KAboutData>
@@ -55,6 +52,8 @@ ElisaApplication::ElisaApplication(QObject *parent) : \
QObject(parent)  #endif
 {
     setupActions();
+
+    mConfigurationDialog.addModule(QStringLiteral("kcm_elisa_local_file"));
 }
 
 void ElisaApplication::setupActions()
@@ -78,8 +77,15 @@ void ElisaApplication::setupActions()
         mCollection.addAction(mAboutAppAction->objectName(), mAboutAppAction);
     }
 
-    auto mKeyBindignsAction = KStandardAction::keyBindings(this, \
                &ElisaApplication::configureShortcuts, this);
-    mCollection.addAction(mKeyBindignsAction->objectName(), mKeyBindignsAction);
+    if (KAuthorized::authorizeAction(QStringLiteral("options_configure"))) {
+        auto mPreferencesAction = KStandardAction::preferences(this, \
&ElisaApplication::configureElisa, this); +        \
mCollection.addAction(mPreferencesAction->objectName(), mPreferencesAction); +    }
+
+    if (KAuthorized::authorizeAction(QStringLiteral("options_configure_keybinding"))) \
{ +        auto mKeyBindignsAction = KStandardAction::keyBindings(this, \
&ElisaApplication::configureShortcuts, this); +        \
mCollection.addAction(mKeyBindignsAction->objectName(), mKeyBindignsAction); +    }
 
     mCollection.readSettings();
 #endif
@@ -124,6 +130,12 @@ void ElisaApplication::configureShortcuts()
 #endif
 }
 
+void ElisaApplication::configureElisa()
+{
+    mConfigurationDialog.setModal(true);
+    mConfigurationDialog.show();
+}
+
 QAction * ElisaApplication::action(const QString& name)
 {
 #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND
diff --git a/src/elisaapplication.h b/src/elisaapplication.h
index 46c06b2..81610ba 100644
--- a/src/elisaapplication.h
+++ b/src/elisaapplication.h
@@ -26,6 +26,8 @@
 #include <KXmlGui/KActionCollection>
 #endif
 
+#include <KCMultiDialog>
+
 #include <QObject>
 #include <QString>
 
@@ -56,12 +58,16 @@ public Q_SLOTS:
 
     void configureShortcuts();
 
+    void configureElisa();
+
 private:
 
 #if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND
     KActionCollection mCollection;
 #endif
 
+    KCMultiDialog mConfigurationDialog;
+
 };
 
 #endif // ELISAAPPLICATION_H
diff --git a/src/file/filelistener.cpp b/src/file/filelistener.cpp
index cabcbe2..a8ba8e3 100644
--- a/src/file/filelistener.cpp
+++ b/src/file/filelistener.cpp
@@ -28,10 +28,13 @@ class FileListenerPrivate
 {
 public:
 
+    LocalFileListing mLocalFileIndexer;
+
 };
 
-FileListener::FileListener(QObject *parent) : AbstractFileListener(new \
LocalFileListing, parent), d(new FileListenerPrivate) \
+FileListener::FileListener(QObject *parent) : AbstractFileListener(parent), d(new \
FileListenerPrivate)  {
+    setFileListing(&d->mLocalFileIndexer);
 }
 
 FileListener::~FileListener()
@@ -39,5 +42,15 @@ FileListener::~FileListener()
     delete d;
 }
 
+const LocalFileListing& FileListener::localFileIndexer() const
+{
+    return d->mLocalFileIndexer;
+}
+
+void FileListener::setRootPath(const QString &rootPath)
+{
+    d->mLocalFileIndexer.setRootPath(rootPath);
+}
+
 
 #include "moc_filelistener.cpp"
diff --git a/src/file/filelistener.h b/src/file/filelistener.h
index c5e4fa7..47a4a31 100644
--- a/src/file/filelistener.h
+++ b/src/file/filelistener.h
@@ -27,8 +27,7 @@
 #include <QString>
 
 class FileListenerPrivate;
-class DatabaseInterface;
-class MusicAudioTrack;
+class LocalFileListing;
 
 class FileListener : public AbstractFileListener
 {
@@ -39,10 +38,14 @@ public:
 
     virtual ~FileListener();
 
+    const LocalFileListing& localFileIndexer() const;
+
 Q_SIGNALS:
 
 public Q_SLOTS:
 
+    void setRootPath(const QString &rootPath);
+
 private:
 
     FileListenerPrivate *d = nullptr;
diff --git a/src/file/localfilelisting.cpp b/src/file/localfilelisting.cpp
index 92a7692..7553717 100644
--- a/src/file/localfilelisting.cpp
+++ b/src/file/localfilelisting.cpp
@@ -49,13 +49,6 @@ public:
 
 LocalFileListing::LocalFileListing(QObject *parent) : \
AbstractFileListing(QStringLiteral("local"), parent), d(new LocalFileListingPrivate)  \
                {
-    const auto &musicLocations(QStandardPaths::standardLocations(QStandardPaths::MusicLocation));
                
-
-    if (musicLocations.isEmpty()) {
-        return;
-    }
-
-    d->mRootPath = musicLocations.first();
 }
 
 LocalFileListing::~LocalFileListing()
@@ -75,11 +68,19 @@ void LocalFileListing::setRootPath(const QString &rootPath)
 
     d->mRootPath = rootPath;
     Q_EMIT rootPathChanged();
+
+    setSourceName(rootPath);
+}
+
+void LocalFileListing::executeInit()
+{
 }
 
 void LocalFileListing::triggerRefreshOfContent()
 {
     scanDirectoryTree(d->mRootPath);
+
+    Q_EMIT indexingFinished();
 }
 
 
diff --git a/src/file/localfilelisting.h b/src/file/localfilelisting.h
index 0fb59cb..fe68ccd 100644
--- a/src/file/localfilelisting.h
+++ b/src/file/localfilelisting.h
@@ -56,6 +56,8 @@ public Q_SLOTS:
 
 private:
 
+    void executeInit() override;
+
     void triggerRefreshOfContent() override;
 
     std::unique_ptr<LocalFileListingPrivate> d;
diff --git a/src/localFileConfiguration/CMakeLists.txt \
b/src/localFileConfiguration/CMakeLists.txt new file mode 100644
index 0000000..468cc89
--- /dev/null
+++ b/src/localFileConfiguration/CMakeLists.txt
@@ -0,0 +1,31 @@
+set(KCM_ELISA_LOCAL_FILE_SRCS
+    localfileconfiguration.cpp
+    kcm_elisa_local_file.desktop
+    package/contents/ui/main.qml
+    package/metadata.desktop
+)
+
+kconfig_add_kcfg_files(KCM_ELISA_LOCAL_FILE_SRCS ../elisa_settings.kcfgc )
+set(KCM_ELISA_LOCAL_FILE_SRCS
+    ${KCM_ELISA_LOCAL_FILE_SRCS}
+    ../elisa_core.kcfg
+)
+
+add_library(kcm_elisa_local_file MODULE ${KCM_ELISA_LOCAL_FILE_SRCS})
+
+target_link_libraries(kcm_elisa_local_file
+    KF5::ConfigCore
+    KF5::CoreAddons
+    KF5::Declarative
+    KF5::I18n
+    KF5::QuickAddons
+    KF5::ConfigWidgets
+)
+
+kcoreaddons_desktop_to_json(kcm_elisa_local_file "kcm_elisa_local_file.desktop")
+
+install(FILES kcm_elisa_local_file.desktop DESTINATION ${SERVICES_INSTALL_DIR})
+install(TARGETS kcm_elisa_local_file DESTINATION ${PLUGIN_INSTALL_DIR}/kcms)
+
+kpackage_install_package(package kcm_elisa_local_file kcms)
+
diff --git a/src/localFileConfiguration/kcm_elisa_local_file.desktop \
b/src/localFileConfiguration/kcm_elisa_local_file.desktop new file mode 100644
index 0000000..fdb3b90
--- /dev/null
+++ b/src/localFileConfiguration/kcm_elisa_local_file.desktop
@@ -0,0 +1,13 @@
+[Desktop Entry]
+Type=Service
+Icon=alarm
+Exec=kcmshell5 kcm_elisa_local_file
+
+X-KDE-Library=kcm_elisa_local_file
+X-KDE-ServiceTypes=KCModule
+X-KDE-ParentComponents=elisa
+X-KDE-PluginKeyword=kcm_elisa_local_file
+
+Name=Elisa Local Files Indexer
+Comment=A configuration tool for Elisa music player local files indexer
+X-KDE-Keywords=Elisa
diff --git a/src/localFileConfiguration/localfileconfiguration.cpp \
b/src/localFileConfiguration/localfileconfiguration.cpp new file mode 100644
index 0000000..508e78a
--- /dev/null
+++ b/src/localFileConfiguration/localfileconfiguration.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2017 Matthieu Gallien <matthieu_gallien@yahoo.fr>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "localfileconfiguration.h"
+
+#include "elisa_settings.h"
+
+#include <KPluginFactory>
+#include <KAboutData>
+#include <KLocalizedString>
+
+#include <QStandardPaths>
+
+K_PLUGIN_FACTORY_WITH_JSON(KCMElisaLocalFileFactory,
+                           "kcm_elisa_local_file.json",
+                           registerPlugin<KCMElisaLocalFile>();)
+
+KCMElisaLocalFile::KCMElisaLocalFile(QObject* parent, const QVariantList &args)
+    : ConfigModule(parent, args)
+{
+    KAboutData *about = new KAboutData(QStringLiteral("kcm_elisa_local_file"),
+                                       i18n("Elisa Local Files Indexer \
Configuration"), +                                       QStringLiteral("0.1"), {}, \
KAboutLicense::LGPL_V3, +                                       i18n("Copyright 20017 \
Matthieu Gallien <matthieu_gallien@yahoo.fr>")); +
+    about->addAuthor(i18n("Matthieu Gallien"),i18n("Author"), \
QStringLiteral("mgallien@mgallien.fr")); +    setAboutData(about);
+
+    auto configurationFileName = \
QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); +    \
configurationFileName += QStringLiteral("/elisarc"); +    \
Elisa::ElisaConfiguration::instance(configurationFileName); +
+    connect(Elisa::ElisaConfiguration::self(), \
&Elisa::ElisaConfiguration::configChanged, +            this, \
&KCMElisaLocalFile::configChanged); +
+    setRootPath(Elisa::ElisaConfiguration::rootPath());
+    Elisa::ElisaConfiguration::setRootPath(mRootPath);
+    Elisa::ElisaConfiguration::self()->save();
+}
+
+KCMElisaLocalFile::~KCMElisaLocalFile()
+{
+}
+
+QStringList KCMElisaLocalFile::rootPath() const
+{
+    return mRootPath;
+}
+
+void KCMElisaLocalFile::defaults()
+{
+    setRootPath(QStandardPaths::standardLocations(QStandardPaths::MusicLocation));
+}
+
+void KCMElisaLocalFile::load()
+{
+    setRootPath(Elisa::ElisaConfiguration::rootPath());
+}
+
+void KCMElisaLocalFile::save()
+{
+    Elisa::ElisaConfiguration::setRootPath(mRootPath);
+    Elisa::ElisaConfiguration::self()->save();
+}
+
+void KCMElisaLocalFile::setRootPath(QStringList rootPath)
+{
+    if (mRootPath == rootPath) {
+        return;
+    }
+
+    mRootPath.clear();
+    for (const auto &onePath : rootPath) {
+        if (onePath.startsWith(QStringLiteral("file:///"))) {
+            mRootPath.push_back(onePath.mid(7));
+        } else if (onePath.startsWith(QStringLiteral("file:/"))) {
+            mRootPath.push_back(onePath.mid(5));
+        } else {
+            mRootPath.push_back(onePath);
+        }
+    }
+
+    if (mRootPath.isEmpty()) {
+        for (const auto &musicPath : \
QStandardPaths::standardLocations(QStandardPaths::MusicLocation)) { +            \
mRootPath.push_back(musicPath); +        }
+    }
+
+    Q_EMIT rootPathChanged(mRootPath);
+
+    setNeedsSave(true);
+    Q_EMIT needsSaveChanged();
+}
+
+void KCMElisaLocalFile::configChanged()
+{
+    setRootPath(Elisa::ElisaConfiguration::rootPath());
+}
+
+
+#include "localfileconfiguration.moc"
diff --git a/src/file/localfilelisting.h \
b/src/localFileConfiguration/localfileconfiguration.h similarity index 57%
copy from src/file/localfilelisting.h
copy to src/localFileConfiguration/localfileconfiguration.h
index 0fb59cb..878d26d 100644
--- a/src/file/localfilelisting.h
+++ b/src/localFileConfiguration/localfileconfiguration.h
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016-2017 Matthieu Gallien <matthieu_gallien@yahoo.fr>
+ * Copyright 2017 Matthieu Gallien <matthieu_gallien@yahoo.fr>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
@@ -17,51 +17,52 @@
  * Boston, MA 02110-1301, USA.
  */
 
-#ifndef LOCALFILELISTING_H
-#define LOCALFILELISTING_H
+#if !defined LOCALFILECONFIGURATION_H_
+#define LOCALFILECONFIGURATION_H_
 
-#include "../abstractfile/abstractfilelisting.h"
+#include <KQuickAddons/ConfigModule>
+#include <QStringList>
 
-#include <QString>
-
-#include <memory>
-
-class LocalFileListingPrivate;
-
-class LocalFileListing : public AbstractFileListing
+class KCMElisaLocalFile : public KQuickAddons::ConfigModule
 {
 
     Q_OBJECT
 
-    Q_PROPERTY(QString rootPath
+    Q_PROPERTY(QStringList rootPath
                READ rootPath
                WRITE setRootPath
                NOTIFY rootPathChanged)
 
 public:
 
-    explicit LocalFileListing(QObject *parent = 0);
+    explicit KCMElisaLocalFile(QObject *parent, const QVariantList &args);
 
-    virtual ~LocalFileListing();
+    virtual ~KCMElisaLocalFile();
 
-    QString rootPath() const;
+    QStringList rootPath() const;
 
 Q_SIGNALS:
 
-    void rootPathChanged();
+    void rootPathChanged(QStringList rootPath);
 
 public Q_SLOTS:
 
-    void setRootPath(const QString &rootPath);
+    void defaults() override final;
 
-private:
+    void load() override final;
 
-    void triggerRefreshOfContent() override;
+    void save() override final;
 
-    std::unique_ptr<LocalFileListingPrivate> d;
+    void setRootPath(QStringList rootPath);
 
-};
+private Q_SLOTS:
 
+    void configChanged();
 
+private:
+
+    QStringList mRootPath;
+
+};
 
-#endif // LOCALFILELISTING_H
+#endif
diff --git a/src/localFileConfiguration/package/contents/ui/main.qml \
b/src/localFileConfiguration/package/contents/ui/main.qml new file mode 100644
index 0000000..5d09733
--- /dev/null
+++ b/src/localFileConfiguration/package/contents/ui/main.qml
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2017 Matthieu Gallien <matthieu_gallien@yahoo.fr>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+import QtQuick 2.7
+import QtQuick.Controls 1.4
+import QtQuick.Layouts 1.3
+import QtQuick.Dialogs 1.2
+import QtQml.Models 2.3
+import org.kde.kcm 1.0
+import org.kde.plasma.core 2.0 as PlasmaCore
+
+Item {
+    //implicitWidth and implicitHeight will be used as initial size
+    //when loaded in kcmshell5
+    implicitWidth: units.gridUnit * 20
+    implicitHeight: units.gridUnit * 20
+
+    ConfigModule.buttons: ConfigModule.Help|ConfigModule.Apply
+
+    SystemPalette {
+        id: myPalette
+        colorGroup: SystemPalette.Active
+    }
+
+    Component {
+        id: highlightBar
+
+        Rectangle {
+            width: 200; height: 50
+            color: myPalette.highlight
+        }
+    }
+
+    Component {
+        id: pathDelegate
+
+        Item {
+            id: delegateItem
+
+            height: 3 * units.gridUnit
+
+            width: pathList.width
+
+            Rectangle {
+                anchors.fill: parent
+                anchors.margins: 0.1 * units.gridUnit
+
+                color: myPalette.base
+
+                MouseArea {
+                    anchors.fill: parent
+
+                    hoverEnabled: true
+
+                    onEntered: pathList.currentIndex = \
delegateItem.DelegateModel.itemsIndex +
+                    Label {
+                        text: modelData
+
+                        anchors.centerIn: parent
+                    }
+
+                    ToolButton {
+                        iconName: 'list-remove'
+
+                        anchors.top: parent.top
+                        anchors.right: parent.right
+
+                        onClicked:
+                        {
+                            var oldPaths = kcm.rootPath
+                            oldPaths.splice(delegateItem.DelegateModel.itemsIndex, \
1) +                            kcm.rootPath = oldPaths
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    RowLayout {
+        spacing: 0
+
+        anchors.fill: parent
+
+        ScrollView {
+            flickableItem.boundsBehavior: Flickable.StopAtBounds
+
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+
+            ListView {
+                id:pathList
+
+                anchors.fill: parent
+
+                model: DelegateModel {
+                    model: kcm.rootPath
+
+                    delegate: pathDelegate
+                }
+
+                highlight: highlightBar
+            }
+        }
+
+        ColumnLayout {
+            Layout.fillHeight: true
+            Layout.leftMargin: 0.3 * units.gridUnit
+
+            Button {
+                text: 'Add new path'
+                onClicked: fileDialog.open()
+
+                Layout.alignment: Qt.AlignTop | Qt.AlignLeft
+
+                FileDialog {
+                    id: fileDialog
+                    title: "Please choose a file"
+                    folder: shortcuts.home
+                    selectFolder: true
+
+                    visible: false
+
+                    onAccepted: {
+                        var oldPaths = kcm.rootPath
+                        oldPaths.push(fileDialog.fileUrls)
+                        kcm.rootPath = oldPaths
+                    }
+                }
+            }
+
+            Item {
+                Layout.fillHeight: true
+            }
+        }
+    }
+}
diff --git a/src/localFileConfiguration/package/metadata.desktop \
b/src/localFileConfiguration/package/metadata.desktop new file mode 100644
index 0000000..7ffb54b
--- /dev/null
+++ b/src/localFileConfiguration/package/metadata.desktop
@@ -0,0 +1,16 @@
+[Desktop Entry]
+Name=Elisa
+Comment=Configure Local Files Indexer of Elisa Music Player
+Icon=elisa
+Type=Service
+X-KDE-ParentApp=
+X-KDE-PluginInfo-Author=Matthieu Gallien
+X-KDE-PluginInfo-Email=matthieu_gallien@yahoo.fr
+X-KDE-PluginInfo-License=LGPLv3
+X-KDE-PluginInfo-Name=kcm_elisa_local_file
+X-KDE-PluginInfo-Version=
+X-KDE-PluginInfo-Website=
+X-KDE-ServiceTypes=Plasma/Generic
+
+X-Plasma-MainScript=ui/main.qml
+X-Plasma-RemoteLocation=
diff --git a/src/musiclistenersmanager.cpp b/src/musiclistenersmanager.cpp
index 1f82789..69483ea 100644
--- a/src/musiclistenersmanager.cpp
+++ b/src/musiclistenersmanager.cpp
@@ -32,13 +32,19 @@
 #include "databaseinterface.h"
 #include "mediaplaylist.h"
 #include "file/filelistener.h"
+#include "file/localfilelisting.h"
 #include "trackslistener.h"
+#include "elisa_settings.h"
 
 #include <QThread>
 #include <QMutex>
 #include <QStandardPaths>
 #include <QDir>
 #include <QCoreApplication>
+#include <QList>
+#include <QScopedPointer>
+#include <QPointer>
+#include <QFileSystemWatcher>
 
 class MusicListenersManagerPrivate
 {
@@ -51,15 +57,17 @@ public:
 #endif
 
 #if defined KF5Baloo_FOUND && KF5Baloo_FOUND
-    BalooListener mBalooListener;
+    QScopedPointer<BalooListener> mBalooListener;
 #endif
 
-#if (!defined KF5Baloo_FOUND || !KF5Baloo_FOUND) && defined KF5FileMetaData_FOUND && \
                KF5FileMetaData_FOUND
-    FileListener mFileListener;
+#if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND
+    QList<QPointer<FileListener>> mFileListener;
 #endif
 
     DatabaseInterface mDatabaseInterface;
 
+    QFileSystemWatcher mConfigFileWatcher;
+
 };
 
 MusicListenersManager::MusicListenersManager(QObject *parent)
@@ -106,6 +114,24 @@ MusicListenersManager::MusicListenersManager(QObject *parent)
 
     connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit,
             this, &MusicListenersManager::applicationAboutToQuit);
+
+    connect(Elisa::ElisaConfiguration::self(), \
&Elisa::ElisaConfiguration::configChanged, +            this, \
&MusicListenersManager::configChanged); +
+    connect(&d->mConfigFileWatcher, &QFileSystemWatcher::fileChanged,
+            this, &MusicListenersManager::configChanged);
+
+    auto initialRootPath = Elisa::ElisaConfiguration::rootPath();
+    if (initialRootPath.isEmpty()) {
+        for (const auto &musicPath : \
QStandardPaths::standardLocations(QStandardPaths::MusicLocation)) { +            \
initialRootPath.push_back(musicPath); +        }
+
+        Elisa::ElisaConfiguration::setRootPath(initialRootPath);
+        Elisa::ElisaConfiguration::self()->save();
+    }
+
+    d->mConfigFileWatcher.addPath(Elisa::ElisaConfiguration::self()->config()->name());
  }
 
 MusicListenersManager::~MusicListenersManager()
@@ -137,31 +163,7 @@ void MusicListenersManager::subscribeForTracks(MediaPlayList \
*client)  
 void MusicListenersManager::databaseReady()
 {
-#if defined KF5Baloo_FOUND && KF5Baloo_FOUND
-    d->mBalooListener.setDatabaseInterface(&d->mDatabaseInterface);
-    d->mBalooListener.moveToThread(&d->mDatabaseThread);
-    connect(this, &MusicListenersManager::applicationIsTerminating,
-            &d->mBalooListener, &BalooListener::applicationAboutToQuit, \
                Qt::BlockingQueuedConnection);
-    connect(this, &MusicListenersManager::databaseIsReady,
-            &d->mBalooListener, &BalooListener::databaseReady);
-#endif
-#if defined UPNPQT_FOUND && UPNPQT_FOUND
-    d->mUpnpListener.setDatabaseInterface(&d->mDatabaseInterface);
-    d->mUpnpListener.moveToThread(&d->mDatabaseThread);
-    connect(this, &MusicListenersManager::applicationIsTerminating,
-            &d->mUpnpListener, &UpnpListener::applicationAboutToQuit, \
                Qt::BlockingQueuedConnection);
-    connect(this, &MusicListenersManager::databaseIsReady,
-            &d->mUpnpListener, &UpnpListener::databaseReady);
-#endif
-
-#if (!defined KF5Baloo_FOUND || !KF5Baloo_FOUND) && defined KF5FileMetaData_FOUND && \
                KF5FileMetaData_FOUND
-    d->mFileListener.setDatabaseInterface(&d->mDatabaseInterface);
-    d->mFileListener.moveToThread(&d->mDatabaseThread);
-    connect(this, &MusicListenersManager::applicationIsTerminating,
-            &d->mFileListener, &FileListener::applicationAboutToQuit, \
                Qt::BlockingQueuedConnection);
-    connect(this, &MusicListenersManager::databaseIsReady,
-            &d->mFileListener, &FileListener::databaseReady);
-#endif
+    configChanged();
 
     Q_EMIT databaseIsReady();
 }
@@ -176,5 +178,83 @@ void MusicListenersManager::applicationAboutToQuit()
     d->mDatabaseThread.wait();
 }
 
+void MusicListenersManager::showConfiguration()
+{
+}
+
+void MusicListenersManager::configChanged()
+{
+    auto currentConfiguration = Elisa::ElisaConfiguration::self();
+
+    d->mConfigFileWatcher.addPath(currentConfiguration->config()->name());
+
+    currentConfiguration->load();
+
+#if defined KF5Baloo_FOUND && KF5Baloo_FOUND
+    if (currentConfiguration->balooIndexer() && !d->mBalooListener) {
+        d->mBalooListener.reset(new BalooListener);
+        d->mBalooListener->setDatabaseInterface(&d->mDatabaseInterface);
+        d->mBalooListener->moveToThread(&d->mDatabaseThread);
+        connect(this, &MusicListenersManager::applicationIsTerminating,
+                d->mBalooListener.data(), &BalooListener::applicationAboutToQuit, \
Qt::BlockingQueuedConnection); +        connect(this, \
&MusicListenersManager::databaseIsReady, +                d->mBalooListener.data(), \
&BalooListener::databaseReady); +        connect(d->mBalooListener.data(), \
&BalooListener::indexingFinished, +                this, \
&MusicListenersManager::indexingFinished); +    } else if \
(!currentConfiguration->balooIndexer() && d->mBalooListener) { +        \
d->mBalooListener.reset(); +    }
+#endif
+#if defined UPNPQT_FOUND && UPNPQT_FOUND
+    d->mUpnpListener.setDatabaseInterface(&d->mDatabaseInterface);
+    d->mUpnpListener.moveToThread(&d->mDatabaseThread);
+    connect(this, &MusicListenersManager::applicationIsTerminating,
+            &d->mUpnpListener, &UpnpListener::applicationAboutToQuit, \
Qt::BlockingQueuedConnection); +    connect(this, \
&MusicListenersManager::databaseIsReady, +            &d->mUpnpListener, \
&UpnpListener::databaseReady); +#endif
+
+#if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND
+    if (currentConfiguration->elisaFilesIndexer())
+    {
+        const auto &allRootPaths = currentConfiguration->rootPath();
+        for (auto itFileListener = d->mFileListener.begin(); itFileListener != \
d->mFileListener.end(); ) { +            const auto &currentRootPath = \
(*itFileListener)->localFileIndexer().rootPath(); +            auto itPath = \
std::find(allRootPaths.begin(), allRootPaths.end(), currentRootPath); +
+            if (itPath == allRootPaths.end()) {
+                d->mDatabaseInterface.removeAllTracksFromSource((*itFileListener)->fileListing()->sourceName());
 +                itFileListener = d->mFileListener.erase(itFileListener);
+            } else {
+                ++itFileListener;
+            }
+        }
+
+        for (const auto &oneRootPath : allRootPaths) {
+            auto itPath = std::find_if(d->mFileListener.begin(), \
d->mFileListener.end(), +                                       [&oneRootPath](auto \
value)->bool {return value->localFileIndexer().rootPath() == oneRootPath;}); +        \
if (itPath == d->mFileListener.end()) { +                auto newFileIndexer = new \
FileListener; +
+                newFileIndexer->setDatabaseInterface(&d->mDatabaseInterface);
+                newFileIndexer->moveToThread(&d->mDatabaseThread);
+                connect(this, &MusicListenersManager::applicationIsTerminating,
+                        newFileIndexer, &FileListener::applicationAboutToQuit, \
Qt::BlockingQueuedConnection); +                connect(this, \
&MusicListenersManager::databaseIsReady, +                        newFileIndexer, \
&FileListener::databaseReady); +                connect(newFileIndexer, \
&FileListener::indexingFinished, +                        this, \
&MusicListenersManager::indexingFinished); +
+                newFileIndexer->setRootPath(oneRootPath);
+
+                d->mFileListener.push_back({newFileIndexer});
+
+                newFileIndexer->performInitialScan();
+            }
+        }
+    }
+#endif
+}
+
 
 #include "moc_musiclistenersmanager.cpp"
diff --git a/src/musiclistenersmanager.h b/src/musiclistenersmanager.h
index 668f770..796ba45 100644
--- a/src/musiclistenersmanager.h
+++ b/src/musiclistenersmanager.h
@@ -77,12 +77,20 @@ Q_SIGNALS:
 
     void databaseIsReady();
 
+    void indexingFinished();
+
 public Q_SLOTS:
 
     void databaseReady();
 
     void applicationAboutToQuit();
 
+    void showConfiguration();
+
+private Q_SLOTS:
+
+    void configChanged();
+
 private:
 
     MusicListenersManagerPrivate *d;
diff --git a/src/upnpControl.cpp b/src/upnpControl.cpp
index ec0c39f..65c9e11 100644
--- a/src/upnpControl.cpp
+++ b/src/upnpControl.cpp
@@ -53,6 +53,7 @@
 #include "elisaapplication.h"
 #include "audiowrapper.h"
 #include "alltracksmodel.h"
+#include "elisa_settings.h"
 
 #if defined Qt5DBus_FOUND && Qt5DBus_FOUND
 #include "mpris2/mpris2.h"
@@ -214,6 +215,12 @@ int main(int argc, char *argv[])
                                               QtAndroid::androidContext().object());
 #endif
 
+    auto configurationFileName = \
QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); +    \
configurationFileName += QStringLiteral("/elisarc"); +    \
Elisa::ElisaConfiguration::instance(configurationFileName); +    \
Elisa::ElisaConfiguration::self()->load(); +    \
Elisa::ElisaConfiguration::self()->save(); +
     QQmlApplicationEngine engine;
     engine.addImportPath(QStringLiteral("qrc:/imports"));
     QQmlFileSelector selector(&engine);


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic