diff --git a/src/MediaServer.qml b/src/MediaServer.qml index 7eb98f04..700fa7a6 100644 --- a/src/MediaServer.qml +++ b/src/MediaServer.qml @@ -1,901 +1,969 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ import QtQuick 2.7 import QtQuick.Controls 1.3 import QtQuick.Controls.Styles 1.3 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 import QtQml.Models 2.1 import org.mgallien.QmlExtension 1.0 import Qt.labs.settings 1.0 ApplicationWindow { id: mainWindow visible: true minimumWidth: 1000 minimumHeight: 600 x: persistentSettings.x y: persistentSettings.y width: persistentSettings.width height: persistentSettings.height title: 'Elisa' property var helpAction: elisa.action("help_contents") property var quitApplication: elisa.action("file_quit") property var reportBugAction: elisa.action("help_report_bug") property var aboutAppAction: elisa.action("help_about_app") property var configureShortcutsAction: elisa.action("options_configure_keybinding") property var configureAction: elisa.action("options_configure") SystemPalette { id: myPalette colorGroup: SystemPalette.Active } Theme { id: elisaTheme } Settings { id: persistentSettings property int x property int y property int width : 1000 property int height : 600 property var playListState property var playListControlerState property var audioPlayerState property double playControlItemVolume : 1.0 property bool playControlItemMuted : false } Action { id: qmlQuitAction text: quitApplication.text shortcut: quitApplication.shortcut iconName: elisa.iconName(quitApplication.icon) onTriggered: quitApplication.trigger() } property string globalBrowseFlag: 'BrowseDirectChildren' property string globalFilter: '*' property string globalSortCriteria: '' Connections { target: Qt.application onAboutToQuit: { persistentSettings.x = mainWindow.x; persistentSettings.y = mainWindow.y; persistentSettings.width = mainWindow.width; persistentSettings.height = mainWindow.height; persistentSettings.playListState = playListModelItem.persistentState; persistentSettings.playListControlerState = playListControlerItem.persistentState; persistentSettings.audioPlayerState = manageAudioPlayer.persistentState persistentSettings.playControlItemVolume = headerBar.playerControl.volume persistentSettings.playControlItemMuted = headerBar.playerControl.muted } } PlatformIntegration { id: platformInterface playListModel: playListModelItem playListControler: playListControlerItem audioPlayerManager: manageAudioPlayer headerBarManager: myHeaderBarManager manageMediaPlayerControl: myPlayControlManager player: audioPlayer onRaisePlayer: { mainWindow.show() mainWindow.raise() mainWindow.requestActivate() } } MusicListenersManager { id: allListeners + + property int cptRunningIndexers: 0 + + onIndexingStarted: + { + ++cptRunningIndexers + console.log("indexing started " + cptRunningIndexers) + } + + onIndexingFinished: + { + --cptRunningIndexers + console.log("indexing finished " + cptRunningIndexers) + + if (cptRunningIndexers === 0) { + if (allAlbumsModel.albumCount() === 0) { + noTrackNotification.visible = true + } else { + noTrackNotification.visible = false + } + } + } } AudioWrapper { id: audioPlayer muted: headerBar.playerControl.muted volume: headerBar.playerControl.volume * 100 onVolumeChanged: headerBar.playerControl.volume = volume / 100.0 onMutedChanged: headerBar.playerControl.muted = muted source: manageAudioPlayer.playerSource onPlaying: { myPlayControlManager.playerPlaying() } onPaused: { myPlayControlManager.playerPaused() } onStopped: { myPlayControlManager.playerStopped() } } MediaPlayList { id: playListModelItem persistentState: persistentSettings.playListState musicListenersManager: allListeners } PlayListControler { id: playListControlerItem playListModel: playListModelItem isValidRole: MediaPlayList.IsValidRole onPlayListFinished: manageAudioPlayer.playListFinished() persistentState: persistentSettings.playListControlerState Component.onCompleted: { var d = new Date(); var n = d.getMilliseconds(); seedRandomGenerator(n); } } ManageHeaderBar { id: myHeaderBarManager playListModel: playListModelItem currentTrack: playListControlerItem.currentTrack artistRole: MediaPlayList.ArtistRole titleRole: MediaPlayList.TitleRole albumRole: MediaPlayList.AlbumRole imageRole: MediaPlayList.ImageRole isValidRole: MediaPlayList.IsValidRole } ManageAudioPlayer { id: manageAudioPlayer currentTrack: playListControlerItem.currentTrack playListModel: playListModelItem urlRole: MediaPlayList.ResourceRole isPlayingRole: MediaPlayList.IsPlayingRole playerStatus: audioPlayer.status playerPlaybackState: audioPlayer.playbackState playerError: audioPlayer.error audioDuration: audioPlayer.duration playerIsSeekable: audioPlayer.seekable playerPosition: audioPlayer.position persistentState: persistentSettings.audioPlayerState onPlayerPlay: audioPlayer.play() onPlayerPause: audioPlayer.pause() onPlayerStop: audioPlayer.stop() onSkipNextTrack: playListControlerItem.skipNextTrack() onSeek: audioPlayer.seek(position) } ManageMediaPlayerControl { id: myPlayControlManager playListModel: playListModelItem currentTrack: playListControlerItem.currentTrack } AllAlbumsModel { id: allAlbumsModel } AllTracksModel { id: allTracksModel } Connections { target: allListeners onAlbumAdded: { busyScanningMusic.running = false allAlbumsModel.albumAdded(newAlbum) } } Connections { target: allListeners onAlbumRemoved: allAlbumsModel.albumRemoved(removedAlbum) } Connections { target: allListeners onAlbumModified: allAlbumsModel.albumModified(modifiedAlbum) } Connections { target: allListeners onTracksAdded: allTracksModel.tracksAdded(allTracks) } Connections { target: allListeners onTrackRemoved: allTracksModel.trackRemoved(id) } Connections { target: allListeners onTrackModified: allTracksModel.trackModified(modifiedTrack) } AllArtistsModel { id: allArtistsModel } Connections { target: allListeners onArtistAdded: allArtistsModel.artistAdded(newArtist) } Connections { target: allListeners onArtistRemoved: allArtistsModel.artistRemoved(removedArtist) } Connections { target: allListeners onArtistModified: allArtistsModel.artistModified(modifiedArtist) } Menu { id: applicationMenu title: i18nc("open application menu", "Application Menu") MenuItem { action: qmlQuitAction visible: qmlQuitAction.text !== "" } MenuSeparator { visible: qmlQuitAction.text !== "" } MenuItem { text: helpAction.text shortcut: helpAction.shortcut iconName: elisa.iconName(helpAction.icon) onTriggered: helpAction.trigger() visible: helpAction.text !== "" } MenuSeparator { visible: helpAction.text !== "" } MenuItem { text: reportBugAction.text shortcut: reportBugAction.shortcut iconName: elisa.iconName(reportBugAction.icon) onTriggered: reportBugAction.trigger() visible: reportBugAction.text !== "" } MenuSeparator { visible: reportBugAction.text !== "" } MenuItem { text: configureAction.text shortcut: configureAction.shortcut iconName: 'configure' onTriggered: configureAction.trigger() visible: configureAction.text !== "" } MenuItem { text: configureShortcutsAction.text shortcut: configureShortcutsAction.shortcut iconName: elisa.iconName(configureShortcutsAction.icon) onTriggered: configureShortcutsAction.trigger() visible: configureShortcutsAction.text !== "" } MenuSeparator { visible: configureAction.text !== "" || configureShortcutsAction.text !== "" } MenuItem { text: aboutAppAction.text shortcut: aboutAppAction.shortcut iconName: elisa.iconName(aboutAppAction.icon) onTriggered: aboutAppAction.trigger() visible: aboutAppAction.text !== "" } } Action { id: applicationMenuAction text: i18nc("open application menu", "Application Menu") iconName: "application-menu" onTriggered: applicationMenu.popup() } Rectangle { color: myPalette.base anchors.fill: parent ColumnLayout { anchors.fill: parent spacing: 0 Item { Layout.preferredHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight Layout.minimumHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight Layout.maximumHeight: mainWindow.height * 0.2 + elisaTheme.mediaPlayerControlHeight Layout.fillWidth: true HeaderBar { id: headerBar anchors.fill: parent tracksCount: myHeaderBarManager.remainingTracks album: myHeaderBarManager.album title: myHeaderBarManager.title artist: myHeaderBarManager.artist image: myHeaderBarManager.image ratingVisible: false playerControl.duration: audioPlayer.duration playerControl.seekable: audioPlayer.seekable playerControl.volume: persistentSettings.playControlItemVolume playerControl.muted: persistentSettings.playControlItemMuted playerControl.position: audioPlayer.position playerControl.skipBackwardEnabled: myPlayControlManager.skipBackwardControlEnabled playerControl.skipForwardEnabled: myPlayControlManager.skipForwardControlEnabled playerControl.playEnabled: myPlayControlManager.playControlEnabled playerControl.isPlaying: myPlayControlManager.musicPlaying playerControl.onSeek: audioPlayer.seek(position) playerControl.onPlay: manageAudioPlayer.playPause() playerControl.onPause: manageAudioPlayer.playPause() playerControl.onPlayPrevious: playListControlerItem.skipPreviousTrack() playerControl.onPlayNext: playListControlerItem.skipNextTrack() ToolButton { id: menuButton action: applicationMenuAction z: 2 anchors { right: parent.right top: parent.top rightMargin: elisaTheme.layoutHorizontalMargin * 3 topMargin: elisaTheme.layoutHorizontalMargin * 3 } } Rectangle { anchors.fill: menuButton z: 1 radius: width / 2 color: myPalette.window } } } RowLayout { Layout.fillHeight: true Layout.fillWidth: true spacing: 0 Rectangle { color: myPalette.window Layout.fillHeight: true Layout.preferredWidth: mainWindow.width * 0.15 Layout.maximumWidth: mainWindow.width * 0.15 ScrollView { flickableItem.boundsBehavior: Flickable.StopAtBounds anchors.fill: parent ListView { id: viewModeView focus: true z: 2 model: DelegateModel { id: pageDelegateModel groups: [ DelegateModelGroup { name: "selected" } ] model: ListModel { id: pageModel } delegate: Rectangle { id: item height: elisaTheme.viewSelectorDelegateHeight width: viewModeView.width color: (DelegateModel.inSelected ? myPalette.highlight : myPalette.window) MouseArea { anchors.fill: parent acceptedButtons: Qt.LeftButton LabelWithToolTip { id: nameLabel anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.right: parent.right anchors.leftMargin: elisaTheme.layoutHorizontalMargin anchors.rightMargin: elisaTheme.layoutHorizontalMargin verticalAlignment: "AlignVCenter" text: model.name color: (item.DelegateModel.inSelected ? myPalette.highlightedText : myPalette.text) } onClicked: { var myGroup = pageDelegateModel.groups[2] if (myGroup.count > 0 && !item.DelegateModel.inSelected) { myGroup.remove(0, myGroup.count) } item.DelegateModel.inSelected = !item.DelegateModel.inSelected if (item.DelegateModel.inSelected) { viewModeView.currentIndex = index } } } } Component.onCompleted: { pageModel.insert(0, {"name": i18nc("Title of the view of the playlist", "Now Playing")}) pageModel.insert(1, {"name": i18nc("Title of the view of all albums", "Albums")}) pageModel.insert(2, {"name": i18nc("Title of the view of all artists", "Artists")}) pageModel.insert(3, {"name": i18nc("Title of the view of all tracks", "Tracks")}) items.get(1).inSelected = 1 viewModeView.currentIndex = 1 } } } } } - Item { + ColumnLayout { Layout.fillHeight: true Layout.fillWidth: true - RowLayout { - anchors.fill: parent - spacing: 0 - - id: contentZone + spacing: 0 - Item { - id: mainContentView + Rectangle { + id: noTrackNotification - Layout.leftMargin: elisaTheme.layoutHorizontalMargin - Layout.rightMargin: elisaTheme.layoutHorizontalMargin + Layout.fillWidth: true + Layout.preferredHeight: elisaTheme.delegateHeight * 2 - Layout.fillHeight: true + color: myPalette.mid - Layout.minimumWidth: 0 - Layout.maximumWidth: 0 - Layout.preferredWidth: 0 + visible: false - //z: 1 - visible: Layout.minimumWidth != 0 + RowLayout { + anchors.fill: parent - BusyIndicator { - id: busyScanningMusic + LabelWithToolTip { + font.pixelSize: elisaTheme.defaultFontPixelSize * 1.5 + text: i18nc("No track found message", "No track have been found") - anchors.fill: parent + Layout.leftMargin: elisaTheme.layoutHorizontalMargin + Layout.alignment: Qt.AlignHCenter + } - anchors.leftMargin: parent.width / 3 - anchors.rightMargin: parent.width / 3 - anchors.topMargin: parent.height / 3 - anchors.bottomMargin: parent.height / 3 + Button { + id: configureListenerButton - opacity: 0.8 + text: i18nc("general configuration menu entry", "Configure Elisa...") + iconName: 'configure' - z: 2 + Layout.leftMargin: elisaTheme.layoutHorizontalMargin + Layout.alignment: Qt.AlignHCenter - running: true + onClicked: configureAction.trigger() + } + Item { + Layout.fillWidth: true } + } + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + + RowLayout { + anchors.fill: parent + + spacing: 0 + + id: contentZone + + Item { + id: mainContentView + + Layout.leftMargin: elisaTheme.layoutHorizontalMargin + Layout.rightMargin: elisaTheme.layoutHorizontalMargin + + Layout.fillHeight: true + + Layout.minimumWidth: 0 + Layout.maximumWidth: 0 + Layout.preferredWidth: 0 + + //z: 1 + visible: Layout.minimumWidth != 0 - MediaBrowser { - id: localAlbums + BusyIndicator { + id: busyScanningMusic - anchors.fill: parent + anchors.fill: parent - firstPage: MediaAllAlbumView { - playListModel: playListModelItem - playerControl: manageAudioPlayer - stackView: localAlbums.stackView - musicListener: allListeners - contentDirectoryModel: allAlbumsModel + anchors.leftMargin: parent.width / 3 + anchors.rightMargin: parent.width / 3 + anchors.topMargin: parent.height / 3 + anchors.bottomMargin: parent.height / 3 + + opacity: 0.8 + + z: 2 + + running: true } - visible: opacity > 0 - } + MediaBrowser { + id: localAlbums - MediaBrowser { - id: localArtists + anchors.fill: parent - anchors.fill: parent + firstPage: MediaAllAlbumView { + playListModel: playListModelItem + playerControl: manageAudioPlayer + stackView: localAlbums.stackView + musicListener: allListeners + contentDirectoryModel: allAlbumsModel + } - firstPage: MediaAllArtistView { - playListModel: playListModelItem - artistsModel: allArtistsModel - playerControl: manageAudioPlayer - stackView: localArtists.stackView - musicListener: allListeners - contentDirectoryModel: allAlbumsModel + visible: opacity > 0 } - visible: opacity > 0 - } + MediaBrowser { + id: localArtists - MediaBrowser { - id: localTracks + anchors.fill: parent - anchors.fill: parent + firstPage: MediaAllArtistView { + playListModel: playListModelItem + artistsModel: allArtistsModel + playerControl: manageAudioPlayer + stackView: localArtists.stackView + musicListener: allListeners + contentDirectoryModel: allAlbumsModel + } - firstPage: MediaAllTracksView { - playListModel: playListModelItem - tracksModel: allTracksModel - playerControl: manageAudioPlayer - stackView: localTracks.stackView - musicListener: allListeners + visible: opacity > 0 } - visible: opacity > 0 + MediaBrowser { + id: localTracks + + anchors.fill: parent + + firstPage: MediaAllTracksView { + playListModel: playListModelItem + tracksModel: allTracksModel + playerControl: manageAudioPlayer + stackView: localTracks.stackView + musicListener: allListeners + } + + visible: opacity > 0 + } } - } - Rectangle { - id: firstViewSeparatorItem + Rectangle { + id: firstViewSeparatorItem - border.width: 1 - border.color: myPalette.mid - color: myPalette.mid - visible: true + border.width: 1 + border.color: myPalette.mid + color: myPalette.mid + visible: true - Layout.bottomMargin: elisaTheme.layoutVerticalMargin - Layout.topMargin: elisaTheme.layoutVerticalMargin + Layout.bottomMargin: elisaTheme.layoutVerticalMargin + Layout.topMargin: elisaTheme.layoutVerticalMargin - Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter - Layout.fillHeight: true + Layout.fillHeight: true - Layout.preferredWidth: 1 - Layout.minimumWidth: 1 - Layout.maximumWidth: 1 - } + Layout.preferredWidth: 1 + Layout.minimumWidth: 1 + Layout.maximumWidth: 1 + } - MediaPlayListView { - id: playList + MediaPlayListView { + id: playList - playListModel: playListModelItem - playListControler: playListControlerItem + playListModel: playListModelItem + playListControler: playListControlerItem - randomPlayChecked: playListControlerItem.randomPlayControl - repeatPlayChecked: playListControlerItem.repeatPlayControl + randomPlayChecked: playListControlerItem.randomPlayControl + repeatPlayChecked: playListControlerItem.repeatPlayControl - Layout.fillHeight: true + Layout.fillHeight: true - Layout.minimumWidth: contentZone.width - Layout.maximumWidth: contentZone.width - Layout.preferredWidth: contentZone.width + 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 }) + 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 + Rectangle { + id: viewSeparatorItem - border.width: 1 - border.color: myPalette.mid - color: myPalette.mid - visible: Layout.minimumWidth != 0 + border.width: 1 + border.color: myPalette.mid + color: myPalette.mid + visible: Layout.minimumWidth != 0 - Layout.bottomMargin: elisaTheme.layoutVerticalMargin - Layout.topMargin: elisaTheme.layoutVerticalMargin + Layout.bottomMargin: elisaTheme.layoutVerticalMargin + Layout.topMargin: elisaTheme.layoutVerticalMargin - Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter - Layout.fillHeight: true + Layout.fillHeight: true - Layout.preferredWidth: 1 - Layout.minimumWidth: 1 - Layout.maximumWidth: 1 - } + Layout.preferredWidth: 1 + Layout.minimumWidth: 1 + Layout.maximumWidth: 1 + } - ContextView { - id: albumContext + ContextView { + id: albumContext - Layout.fillHeight: true + Layout.fillHeight: true - Layout.minimumWidth: contentZone.width - Layout.maximumWidth: contentZone.width - Layout.preferredWidth: contentZone.width + Layout.minimumWidth: contentZone.width + Layout.maximumWidth: contentZone.width + Layout.preferredWidth: contentZone.width - visible: Layout.minimumWidth != 0 + visible: Layout.minimumWidth != 0 - artistName: myHeaderBarManager.artist - albumName: myHeaderBarManager.album - albumArtUrl: myHeaderBarManager.image + artistName: myHeaderBarManager.artist + albumName: myHeaderBarManager.album + albumArtUrl: myHeaderBarManager.image + } } } states: [ State { name: 'full' when: viewModeView.currentIndex === 0 PropertyChanges { target: mainContentView Layout.fillWidth: false Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: firstViewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: playList Layout.minimumWidth: contentZone.width / 2 Layout.maximumWidth: contentZone.width / 2 Layout.preferredWidth: contentZone.width / 2 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: albumContext Layout.minimumWidth: contentZone.width / 2 Layout.maximumWidth: contentZone.width / 2 Layout.preferredWidth: contentZone.width / 2 } PropertyChanges { target: localAlbums opacity: 0 } PropertyChanges { target: localArtists opacity: 0 } PropertyChanges { target: localTracks opacity: 0 } }, State { name: 'allAlbums' when: viewModeView.currentIndex === 1 PropertyChanges { target: mainContentView Layout.fillWidth: true Layout.minimumWidth: contentZone.width * 0.64 Layout.maximumWidth: contentZone.width * 0.66 } PropertyChanges { target: firstViewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: playList Layout.minimumWidth: contentZone.width * 0.33 Layout.maximumWidth: contentZone.width * 0.33 Layout.preferredWidth: contentZone.width * 0.33 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: albumContext Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: localAlbums opacity: 1 } PropertyChanges { target: localArtists opacity: 0 } PropertyChanges { target: localTracks opacity: 0 } }, State { name: 'allArtists' when: viewModeView.currentIndex === 2 PropertyChanges { target: mainContentView Layout.fillWidth: true Layout.minimumWidth: contentZone.width * 0.64 Layout.maximumWidth: contentZone.width * 0.66 } PropertyChanges { target: firstViewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: playList Layout.minimumWidth: contentZone.width * 0.33 Layout.maximumWidth: contentZone.width * 0.33 Layout.preferredWidth: contentZone.width * 0.33 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: albumContext Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: localAlbums opacity: 0 } PropertyChanges { target: localArtists opacity: 1 } PropertyChanges { target: localTracks opacity: 0 } }, State { name: 'allTracks' when: viewModeView.currentIndex === 3 PropertyChanges { target: mainContentView Layout.fillWidth: true Layout.minimumWidth: contentZone.width * 0.64 Layout.maximumWidth: contentZone.width * 0.66 } PropertyChanges { target: firstViewSeparatorItem Layout.minimumWidth: 1 Layout.maximumWidth: 1 Layout.preferredWidth: 1 } PropertyChanges { target: playList Layout.minimumWidth: contentZone.width * 0.33 Layout.maximumWidth: contentZone.width * 0.33 Layout.preferredWidth: contentZone.width * 0.33 } PropertyChanges { target: viewSeparatorItem Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: albumContext Layout.minimumWidth: 0 Layout.maximumWidth: 0 Layout.preferredWidth: 0 } PropertyChanges { target: localAlbums opacity: 0 } PropertyChanges { target: localArtists opacity: 0 } PropertyChanges { target: localTracks opacity: 1 } } ] transitions: Transition { NumberAnimation { properties: "Layout.minimumWidth, Layout.maximumWidth, Layout.preferredWidth, opacity" easing.type: Easing.InOutQuad duration: 300 } } } } } } } diff --git a/src/abstractfile/abstractfilelistener.cpp b/src/abstractfile/abstractfilelistener.cpp index 132c5ef8..40213614 100644 --- a/src/abstractfile/abstractfilelistener.cpp +++ b/src/abstractfile/abstractfilelistener.cpp @@ -1,99 +1,101 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "abstractfilelistener.h" #include "abstractfilelisting.h" #include "databaseinterface.h" #include class AbstractFileListenerPrivate { public: explicit AbstractFileListenerPrivate() { } QThread mFileQueryThread; AbstractFileListing *mFileListing = nullptr; }; AbstractFileListener::AbstractFileListener(QObject *parent) : QObject(parent), d(new AbstractFileListenerPrivate) { } AbstractFileListener::~AbstractFileListener() { delete d; } DatabaseInterface *AbstractFileListener::databaseInterface() const { return nullptr; } void AbstractFileListener::setDatabaseInterface(DatabaseInterface *model) { if (model) { connect(this, &AbstractFileListener::databaseReady, d->mFileListing, &AbstractFileListing::databaseIsReady); connect(this, &AbstractFileListener::newTrackFile, d->mFileListing, &AbstractFileListing::newTrackFile); connect(d->mFileListing, &AbstractFileListing::tracksList, model, &DatabaseInterface::insertTracksList); connect(d->mFileListing, &AbstractFileListing::removedTracksList, model, &DatabaseInterface::removeTracksList); connect(d->mFileListing, &AbstractFileListing::modifyTracksList, model, &DatabaseInterface::modifyTracksList); QMetaObject::invokeMethod(d->mFileListing, "init", Qt::QueuedConnection); } Q_EMIT databaseInterfaceChanged(); } void AbstractFileListener::applicationAboutToQuit() { d->mFileListing->applicationAboutToQuit(); d->mFileQueryThread.exit(); d->mFileQueryThread.wait(); } void AbstractFileListener::setFileListing(AbstractFileListing *fileIndexer) { d->mFileListing = fileIndexer; d->mFileQueryThread.start(); d->mFileListing->moveToThread(&d->mFileQueryThread); + connect(fileIndexer, &AbstractFileListing::indexingStarted, + this, &AbstractFileListener::indexingStarted); 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 aa34a24f..7c10dde3 100644 --- a/src/abstractfile/abstractfilelistener.h +++ b/src/abstractfile/abstractfilelistener.h @@ -1,80 +1,82 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ABSTRACTFILELISTENER_H #define ABSTRACTFILELISTENER_H #include #include #include class AbstractFileListenerPrivate; class DatabaseInterface; class MusicAudioTrack; class AbstractFileListing; class AbstractFileListener : public QObject { Q_OBJECT Q_PROPERTY(DatabaseInterface* databaseInterface READ databaseInterface WRITE setDatabaseInterface NOTIFY databaseInterfaceChanged) public: explicit AbstractFileListener(QObject *parent = 0); virtual ~AbstractFileListener(); DatabaseInterface* databaseInterface() const; AbstractFileListing* fileListing() const; Q_SIGNALS: void databaseInterfaceChanged(); void databaseReady(); void newTrackFile(const MusicAudioTrack &newTrack); + void indexingStarted(); + void indexingFinished(); void configurationChanged(); public Q_SLOTS: void performInitialScan(); void setDatabaseInterface(DatabaseInterface* databaseInterface); void applicationAboutToQuit(); protected: void setFileListing(AbstractFileListing *fileIndexer); private: AbstractFileListenerPrivate *d = nullptr; }; #endif // ABSTRACTFILELISTENER_H diff --git a/src/abstractfile/abstractfilelisting.h b/src/abstractfile/abstractfilelisting.h index 4b2561af..31499f08 100644 --- a/src/abstractfile/abstractfilelisting.h +++ b/src/abstractfile/abstractfilelisting.h @@ -1,111 +1,113 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef ABSTRACTFILELISTING_H #define ABSTRACTFILELISTING_H #include #include #include #include #include #include class AbstractFileListingPrivate; class MusicAudioTrack; class AbstractFileListing : public QObject { Q_OBJECT public: explicit AbstractFileListing(const QString &sourceName, QObject *parent = 0); virtual ~AbstractFileListing(); virtual void applicationAboutToQuit(); const QString &sourceName() const; Q_SIGNALS: void tracksList(const QList &tracks, const QHash &covers, const QString &musicSource); void removedTracksList(const QList &removedTracks); void modifyTracksList(const QList &modifiedTracks, const QHash &covers); + void indexingStarted(); + void indexingFinished(); public Q_SLOTS: void refreshContent(); void init(); void databaseIsReady(); void newTrackFile(const MusicAudioTrack &partialTrack); protected Q_SLOTS: void directoryChanged(const QString &path); void fileChanged(const QString &modifiedFileName); protected: virtual void executeInit(); virtual void triggerRefreshOfContent(); void scanDirectory(QList &newFiles, const QUrl &path); virtual MusicAudioTrack scanOneFile(const QUrl &scanFile); void watchPath(const QString &pathName); void addFileInDirectory(const QUrl &newFile, const QUrl &directoryName); void scanDirectoryTree(const QString &path); void setHandleNewFiles(bool handleThem); void emitNewFiles(const QList &tracks); void addCover(const MusicAudioTrack &newTrack); void removeDirectory(const QUrl &removedDirectory, QList &allRemovedFiles); void removeFile(const QUrl &oneRemovedTrack, QList &allRemovedFiles); void setSourceName(const QString &name); private: std::unique_ptr d; }; #endif // ABSTRACTFILELISTING_H diff --git a/src/baloo/localbaloofilelisting.cpp b/src/baloo/localbaloofilelisting.cpp index a93d429c..cafe40d4 100644 --- a/src/baloo/localbaloofilelisting.cpp +++ b/src/baloo/localbaloofilelisting.cpp @@ -1,244 +1,246 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "localbaloofilelisting.h" #include "musicaudiotrack.h" #include #include #include #include #include #include #include #include #include #include #include #include #include class LocalBalooFileListingPrivate { public: Baloo::Query mQuery; QHash> mAllAlbums; QHash> mNewAlbums; QList mNewTracks; QHash mAllAlbumCover; QAtomicInt mStopRequest = 0; }; LocalBalooFileListing::LocalBalooFileListing(QObject *parent) : AbstractFileListing(QStringLiteral("baloo"), parent), d(new LocalBalooFileListingPrivate) { d->mQuery.addType(QStringLiteral("Audio")); setHandleNewFiles(false); } LocalBalooFileListing::~LocalBalooFileListing() { } void LocalBalooFileListing::applicationAboutToQuit() { d->mStopRequest = 1; } void LocalBalooFileListing::newBalooFile(const QString &fileName) { auto newFile = QUrl::fromLocalFile(fileName); auto newTrack = scanOneFile(newFile); if (newTrack.isValid()) { QFileInfo newFileInfo(fileName); addFileInDirectory(newFile, QUrl::fromLocalFile(newFileInfo.absoluteDir().absolutePath())); emitNewFiles({newTrack}); } } void LocalBalooFileListing::executeInit() { auto sessionBus = QDBusConnection::sessionBus(); auto methodCall = QDBusMessage::createMethodCall(QStringLiteral("org.kde.baloo"), QStringLiteral("/fileindexer"), QStringLiteral("org.kde.baloo.fileindexer"), QStringLiteral("registerMonitor")); auto answer = sessionBus.call(methodCall); if (answer.type() != QDBusMessage::ReplyMessage) { qDebug() << "LocalBalooFileListing::executeInit" << answer.errorName() << answer.errorMessage(); } sessionBus.connect(QStringLiteral("org.kde.baloo"), QStringLiteral("/fileindexer"), QStringLiteral("org.kde.baloo.fileindexer"), QStringLiteral("finishedIndexingFile"), this, SLOT(newBalooFile(QString))); } void LocalBalooFileListing::triggerRefreshOfContent() { + Q_EMIT indexingStarted(); + auto resultIterator = d->mQuery.exec(); auto newFiles = QList(); while(resultIterator.next() && d->mStopRequest == 0) { const auto &newFileUrl = QUrl::fromLocalFile(resultIterator.filePath()); auto scanFileInfo = QFileInfo(resultIterator.filePath()); const auto currentDirectory = QUrl::fromLocalFile(scanFileInfo.absoluteDir().absolutePath()); addFileInDirectory(newFileUrl, currentDirectory); const auto &newTrack = scanOneFile(newFileUrl); if (newTrack.isValid()) { newFiles.push_back(newTrack); } } if (!newFiles.isEmpty() && d->mStopRequest == 0) { emitNewFiles(newFiles); } Q_EMIT indexingFinished(); } MusicAudioTrack LocalBalooFileListing::scanOneFile(const QUrl &scanFile) { auto newTrack = MusicAudioTrack(); auto fileName = scanFile.toLocalFile(); auto scanFileInfo = QFileInfo(fileName); if (scanFileInfo.exists()) { watchPath(fileName); } Baloo::File match(fileName); match.load(); const auto &allProperties = match.properties(); auto titleProperty = allProperties.find(KFileMetaData::Property::Title); auto durationProperty = allProperties.find(KFileMetaData::Property::Duration); auto artistProperty = allProperties.find(KFileMetaData::Property::Artist); auto albumProperty = allProperties.find(KFileMetaData::Property::Album); auto albumArtistProperty = allProperties.find(KFileMetaData::Property::AlbumArtist); auto trackNumberProperty = allProperties.find(KFileMetaData::Property::TrackNumber); auto discNumberProperty = allProperties.find(KFileMetaData::Property::DiscNumber); auto fileData = KFileMetaData::UserMetaData(fileName); if (albumProperty != allProperties.end()) { auto albumValue = albumProperty->toString(); auto &allTracks = d->mAllAlbums[albumValue]; newTrack.setAlbumName(albumValue); if (artistProperty != allProperties.end()) { newTrack.setArtist(artistProperty->toString()); } if (durationProperty != allProperties.end()) { newTrack.setDuration(QTime::fromMSecsSinceStartOfDay(1000 * durationProperty->toDouble())); } if (titleProperty != allProperties.end()) { newTrack.setTitle(titleProperty->toString()); } if (trackNumberProperty != allProperties.end()) { newTrack.setTrackNumber(trackNumberProperty->toInt()); } if (discNumberProperty != allProperties.end()) { newTrack.setDiscNumber(discNumberProperty->toInt()); } if (albumArtistProperty != allProperties.end()) { newTrack.setAlbumArtist(albumArtistProperty->toString()); } if (newTrack.artist().isEmpty()) { newTrack.setArtist(newTrack.albumArtist()); } newTrack.setRating(fileData.rating()); newTrack.setResourceURI(scanFile); QFileInfo coverFilePath(scanFileInfo.dir().filePath(QStringLiteral("cover.jpg"))); if (coverFilePath.exists()) { d->mAllAlbumCover[albumValue] = QUrl::fromLocalFile(coverFilePath.absoluteFilePath()); } auto itTrack = std::find(allTracks.begin(), allTracks.end(), newTrack); if (itTrack == allTracks.end()) { allTracks.push_back(newTrack); d->mNewTracks.push_back(newTrack); d->mNewAlbums[newTrack.albumName()].push_back(newTrack); auto &newTracks = d->mAllAlbums[newTrack.albumName()]; std::sort(allTracks.begin(), allTracks.end()); std::sort(newTracks.begin(), newTracks.end()); } if (newTrack.title().isEmpty()) { return newTrack; } if (newTrack.artist().isEmpty()) { return newTrack; } if (newTrack.albumName().isEmpty()) { return newTrack; } if (!newTrack.duration().isValid()) { return newTrack; } newTrack.setValid(true); } if (!newTrack.isValid()) { newTrack = AbstractFileListing::scanOneFile(scanFile); } if (newTrack.isValid()) { addCover(newTrack); } return newTrack; } #include "moc_localbaloofilelisting.cpp" diff --git a/src/file/localfilelisting.cpp b/src/file/localfilelisting.cpp index 7553717c..01a82475 100644 --- a/src/file/localfilelisting.cpp +++ b/src/file/localfilelisting.cpp @@ -1,87 +1,89 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "localfilelisting.h" #include "musicaudiotrack.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include class LocalFileListingPrivate { public: QString mRootPath; }; LocalFileListing::LocalFileListing(QObject *parent) : AbstractFileListing(QStringLiteral("local"), parent), d(new LocalFileListingPrivate) { } LocalFileListing::~LocalFileListing() { } QString LocalFileListing::rootPath() const { return d->mRootPath; } void LocalFileListing::setRootPath(const QString &rootPath) { if (d->mRootPath == rootPath) { return; } d->mRootPath = rootPath; Q_EMIT rootPathChanged(); setSourceName(rootPath); } void LocalFileListing::executeInit() { } void LocalFileListing::triggerRefreshOfContent() { + Q_EMIT indexingStarted(); + scanDirectoryTree(d->mRootPath); Q_EMIT indexingFinished(); } #include "moc_localfilelisting.cpp" diff --git a/src/musiclistenersmanager.cpp b/src/musiclistenersmanager.cpp index 69483eaa..4df10f89 100644 --- a/src/musiclistenersmanager.cpp +++ b/src/musiclistenersmanager.cpp @@ -1,260 +1,262 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "musiclistenersmanager.h" #include "config-upnp-qt.h" #if defined UPNPQT_FOUND && UPNPQT_FOUND #include "upnp/upnplistener.h" #endif #if defined KF5Baloo_FOUND && KF5Baloo_FOUND #include "baloo/baloolistener.h" #endif #include "databaseinterface.h" #include "mediaplaylist.h" #include "file/filelistener.h" #include "file/localfilelisting.h" #include "trackslistener.h" #include "elisa_settings.h" #include #include #include #include #include #include #include #include #include class MusicListenersManagerPrivate { public: QThread mDatabaseThread; #if defined UPNPQT_FOUND && UPNPQT_FOUND UpnpListener mUpnpListener; #endif #if defined KF5Baloo_FOUND && KF5Baloo_FOUND QScopedPointer mBalooListener; #endif #if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND QList> mFileListener; #endif DatabaseInterface mDatabaseInterface; QFileSystemWatcher mConfigFileWatcher; }; MusicListenersManager::MusicListenersManager(QObject *parent) : QObject(parent), d(new MusicListenersManagerPrivate) { d->mDatabaseThread.start(); d->mDatabaseInterface.moveToThread(&d->mDatabaseThread); connect(&d->mDatabaseInterface, &DatabaseInterface::requestsInitDone, this, &MusicListenersManager::databaseReady); const auto &localDataPaths = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); auto databaseFileName = QString(); if (!localDataPaths.isEmpty()) { QDir myDataDirectory; myDataDirectory.mkpath(localDataPaths.first()); databaseFileName = localDataPaths.first() + QStringLiteral("/elisaDatabase.db"); } QMetaObject::invokeMethod(&d->mDatabaseInterface, "init", Qt::QueuedConnection, Q_ARG(QString, QStringLiteral("listeners")), Q_ARG(QString, databaseFileName)); connect(&d->mDatabaseInterface, &DatabaseInterface::artistAdded, this, &MusicListenersManager::artistAdded); connect(&d->mDatabaseInterface, &DatabaseInterface::albumAdded, this, &MusicListenersManager::albumAdded); connect(&d->mDatabaseInterface, &DatabaseInterface::trackAdded, this, &MusicListenersManager::trackAdded); connect(&d->mDatabaseInterface, &DatabaseInterface::tracksAdded, this, &MusicListenersManager::tracksAdded); connect(&d->mDatabaseInterface, &DatabaseInterface::artistRemoved, this, &MusicListenersManager::artistRemoved); connect(&d->mDatabaseInterface, &DatabaseInterface::albumRemoved, this, &MusicListenersManager::albumRemoved); connect(&d->mDatabaseInterface, &DatabaseInterface::trackRemoved, this, &MusicListenersManager::trackRemoved); connect(&d->mDatabaseInterface, &DatabaseInterface::artistModified, this, &MusicListenersManager::artistModified); connect(&d->mDatabaseInterface, &DatabaseInterface::albumModified, this, &MusicListenersManager::albumModified); connect(&d->mDatabaseInterface, &DatabaseInterface::trackModified, this, &MusicListenersManager::trackModified); connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &MusicListenersManager::applicationAboutToQuit); connect(Elisa::ElisaConfiguration::self(), &Elisa::ElisaConfiguration::configChanged, this, &MusicListenersManager::configChanged); connect(&d->mConfigFileWatcher, &QFileSystemWatcher::fileChanged, this, &MusicListenersManager::configChanged); auto initialRootPath = Elisa::ElisaConfiguration::rootPath(); if (initialRootPath.isEmpty()) { for (const auto &musicPath : QStandardPaths::standardLocations(QStandardPaths::MusicLocation)) { initialRootPath.push_back(musicPath); } Elisa::ElisaConfiguration::setRootPath(initialRootPath); Elisa::ElisaConfiguration::self()->save(); } d->mConfigFileWatcher.addPath(Elisa::ElisaConfiguration::self()->config()->name()); } MusicListenersManager::~MusicListenersManager() { delete d; } DatabaseInterface *MusicListenersManager::viewDatabase() const { return &d->mDatabaseInterface; } void MusicListenersManager::subscribeForTracks(MediaPlayList *client) { auto helper = new TracksListener(&d->mDatabaseInterface); helper->moveToThread(&d->mDatabaseThread); connect(this, &MusicListenersManager::trackRemoved, helper, &TracksListener::trackRemoved); connect(this, &MusicListenersManager::tracksAdded, helper, &TracksListener::tracksAdded); connect(this, &MusicListenersManager::trackModified, helper, &TracksListener::trackModified); connect(helper, &TracksListener::trackHasChanged, client, &MediaPlayList::trackChanged); connect(helper, &TracksListener::trackHasBeenRemoved, client, &MediaPlayList::trackRemoved); connect(helper, &TracksListener::albumAdded, client, &MediaPlayList::albumAdded); connect(client, &MediaPlayList::newTrackByIdInList, helper, &TracksListener::trackByIdInList); connect(client, &MediaPlayList::newTrackByNameInList, helper, &TracksListener::trackByNameInList); connect(client, &MediaPlayList::newArtistInList, helper, &TracksListener::newArtistInList); } void MusicListenersManager::databaseReady() { configChanged(); Q_EMIT databaseIsReady(); } void MusicListenersManager::applicationAboutToQuit() { d->mDatabaseInterface.applicationAboutToQuit(); Q_EMIT applicationIsTerminating(); d->mDatabaseThread.exit(); d->mDatabaseThread.wait(); } void MusicListenersManager::showConfiguration() { } void MusicListenersManager::configChanged() { auto currentConfiguration = Elisa::ElisaConfiguration::self(); d->mConfigFileWatcher.addPath(currentConfiguration->config()->name()); currentConfiguration->load(); #if defined KF5Baloo_FOUND && KF5Baloo_FOUND if (currentConfiguration->balooIndexer() && !d->mBalooListener) { d->mBalooListener.reset(new BalooListener); d->mBalooListener->setDatabaseInterface(&d->mDatabaseInterface); d->mBalooListener->moveToThread(&d->mDatabaseThread); connect(this, &MusicListenersManager::applicationIsTerminating, d->mBalooListener.data(), &BalooListener::applicationAboutToQuit, Qt::BlockingQueuedConnection); connect(this, &MusicListenersManager::databaseIsReady, d->mBalooListener.data(), &BalooListener::databaseReady); connect(d->mBalooListener.data(), &BalooListener::indexingFinished, this, &MusicListenersManager::indexingFinished); } else if (!currentConfiguration->balooIndexer() && d->mBalooListener) { d->mBalooListener.reset(); } #endif #if defined UPNPQT_FOUND && UPNPQT_FOUND d->mUpnpListener.setDatabaseInterface(&d->mDatabaseInterface); d->mUpnpListener.moveToThread(&d->mDatabaseThread); connect(this, &MusicListenersManager::applicationIsTerminating, &d->mUpnpListener, &UpnpListener::applicationAboutToQuit, Qt::BlockingQueuedConnection); connect(this, &MusicListenersManager::databaseIsReady, &d->mUpnpListener, &UpnpListener::databaseReady); #endif #if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND if (currentConfiguration->elisaFilesIndexer()) { const auto &allRootPaths = currentConfiguration->rootPath(); for (auto itFileListener = d->mFileListener.begin(); itFileListener != d->mFileListener.end(); ) { const auto ¤tRootPath = (*itFileListener)->localFileIndexer().rootPath(); auto itPath = std::find(allRootPaths.begin(), allRootPaths.end(), currentRootPath); if (itPath == allRootPaths.end()) { d->mDatabaseInterface.removeAllTracksFromSource((*itFileListener)->fileListing()->sourceName()); itFileListener = d->mFileListener.erase(itFileListener); } else { ++itFileListener; } } for (const auto &oneRootPath : allRootPaths) { auto itPath = std::find_if(d->mFileListener.begin(), d->mFileListener.end(), [&oneRootPath](auto value)->bool {return value->localFileIndexer().rootPath() == oneRootPath;}); if (itPath == d->mFileListener.end()) { auto newFileIndexer = new FileListener; newFileIndexer->setDatabaseInterface(&d->mDatabaseInterface); newFileIndexer->moveToThread(&d->mDatabaseThread); connect(this, &MusicListenersManager::applicationIsTerminating, newFileIndexer, &FileListener::applicationAboutToQuit, Qt::BlockingQueuedConnection); connect(this, &MusicListenersManager::databaseIsReady, newFileIndexer, &FileListener::databaseReady); + connect(newFileIndexer, &FileListener::indexingStarted, + this, &MusicListenersManager::indexingStarted); 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 796ba453..fe4d4c6b 100644 --- a/src/musiclistenersmanager.h +++ b/src/musiclistenersmanager.h @@ -1,100 +1,102 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef MUSICLISTENERSMANAGER_H #define MUSICLISTENERSMANAGER_H #include #include "musicalbum.h" #include "musicartist.h" #include "musicaudiotrack.h" class MusicListenersManagerPrivate; class DatabaseInterface; class MediaPlayList; class MusicListenersManager : public QObject { Q_OBJECT Q_PROPERTY(DatabaseInterface* viewDatabase READ viewDatabase NOTIFY viewDatabaseChanged) public: explicit MusicListenersManager(QObject *parent = 0); virtual ~MusicListenersManager(); DatabaseInterface* viewDatabase() const; void subscribeForTracks(MediaPlayList *client); Q_SIGNALS: void viewDatabaseChanged(); void artistAdded(const MusicArtist &newArtist); void albumAdded(const MusicAlbum &newAlbum); void trackAdded(qulonglong id); void tracksAdded(const QList &allTracks); void artistRemoved(const MusicArtist &removedArtist); void albumRemoved(const MusicAlbum &removedAlbum, qulonglong removedAlbumId); void trackRemoved(qulonglong id); void artistModified(const MusicArtist &modifiedArtist); void albumModified(const MusicAlbum &modifiedAlbum, qulonglong modifiedAlbumId); void trackModified(const MusicAudioTrack &modifiedTrack); void applicationIsTerminating(); void databaseIsReady(); + void indexingStarted(); + void indexingFinished(); public Q_SLOTS: void databaseReady(); void applicationAboutToQuit(); void showConfiguration(); private Q_SLOTS: void configChanged(); private: MusicListenersManagerPrivate *d; }; #endif // MUSICLISTENERSMANAGER_H