diff --git a/autotests/filescannertest.cpp b/autotests/filescannertest.cpp index 4965c14c..77bd29a8 100644 --- a/autotests/filescannertest.cpp +++ b/autotests/filescannertest.cpp @@ -1,144 +1,141 @@ /* * Copyright 2018 Alexander Stippich * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "musicaudiotrack.h" #include "filescanner.h" #include "config-upnp-qt.h" #include #include #include #include #include -#include #include #include class FileScannerTest: public QObject { Q_OBJECT public: QString createTrackUrl(QString subpath) { return QStringLiteral(LOCAL_FILE_TESTS_SAMPLE_FILES_PATH) + QStringLiteral("/cover_art") + subpath; } QList mTestTracksForDirectory = { createTrackUrl(QStringLiteral("/artist1/album1/not_existing.ogg")), createTrackUrl(QStringLiteral("/artist1/album2/not_existing.ogg")), createTrackUrl(QStringLiteral("/artist1/album3/not_existing.ogg")), createTrackUrl(QStringLiteral("/artist2/album1/not_existing.ogg")), createTrackUrl(QStringLiteral("/artist2/album2/not_existing.ogg")), createTrackUrl(QStringLiteral("/artist2/album3/not_existing.ogg")), createTrackUrl(QStringLiteral("/artist3/album1/not_existing.ogg")), createTrackUrl(QStringLiteral("/artist3/album2/not_existing.ogg")), createTrackUrl(QStringLiteral("/artist3/album3/not_existing.ogg")), }; QList mTestTracksForMetaData = { createTrackUrl(QStringLiteral("/artist4/test.ogg")), createTrackUrl(QStringLiteral("/artist4/test.flac")), createTrackUrl(QStringLiteral("/artist4/test.mp3")), }; private Q_SLOTS: void initTestCase() { } void testFileMetaDataScan() { - QMimeDatabase mimeDb; FileScanner fileScanner; - auto scannedTrack = fileScanner.scanOneFile(QUrl::fromLocalFile(QStringLiteral(LOCAL_FILE_TESTS_SAMPLE_FILES_PATH) + QStringLiteral("/music/test.ogg")), mimeDb); + auto scannedTrack = fileScanner.scanOneFile(QUrl::fromLocalFile(QStringLiteral(LOCAL_FILE_TESTS_SAMPLE_FILES_PATH) + QStringLiteral("/music/test.ogg"))); QCOMPARE(scannedTrack.title(), QStringLiteral("Title")); QCOMPARE(scannedTrack.genre(), QStringLiteral("Genre")); QCOMPARE(scannedTrack.album(), QStringLiteral("Test")); QCOMPARE(scannedTrack.artist(), QStringLiteral("Artist")); } void testFindCoverInDirectory() { FileScanner fileScanner; QVERIFY(!fileScanner.searchForCoverFile(mTestTracksForDirectory.at(0)).isEmpty()); QVERIFY(!fileScanner.searchForCoverFile(mTestTracksForDirectory.at(1)).isEmpty()); QVERIFY(!fileScanner.searchForCoverFile(mTestTracksForDirectory.at(2)).isEmpty()); QVERIFY(!fileScanner.searchForCoverFile(mTestTracksForDirectory.at(3)).isEmpty()); QVERIFY(!fileScanner.searchForCoverFile(mTestTracksForDirectory.at(4)).isEmpty()); QVERIFY(!fileScanner.searchForCoverFile(mTestTracksForDirectory.at(5)).isEmpty()); QVERIFY(!fileScanner.searchForCoverFile(mTestTracksForDirectory.at(6)).isEmpty()); QVERIFY(!fileScanner.searchForCoverFile(mTestTracksForDirectory.at(7)).isEmpty()); QVERIFY(!fileScanner.searchForCoverFile(mTestTracksForDirectory.at(8)).isEmpty()); } void loadCoverFromMetaData() { FileScanner fileScanner; QVERIFY(fileScanner.checkEmbeddedCoverImage(mTestTracksForMetaData.at(0))); QVERIFY(fileScanner.checkEmbeddedCoverImage(mTestTracksForMetaData.at(1))); QVERIFY(fileScanner.checkEmbeddedCoverImage(mTestTracksForMetaData.at(2))); } void benchmarkFileScan() { - QMimeDatabase mimeDb; FileScanner fileScanner; QBENCHMARK { for (int i = 0; i < 100; i++) { - auto scannedTrack = fileScanner.scanOneFile(QUrl::fromLocalFile(QStringLiteral(LOCAL_FILE_TESTS_SAMPLE_FILES_PATH) + QStringLiteral("/music/test.ogg")), mimeDb); - auto scannedTrack2 = fileScanner.scanOneFile(QUrl::fromLocalFile(QStringLiteral(LOCAL_FILE_TESTS_SAMPLE_FILES_PATH) + QStringLiteral("/music/testMultiple.ogg")), mimeDb); - auto scannedTrack3 = fileScanner.scanOneFile(QUrl::fromLocalFile(QStringLiteral(LOCAL_FILE_TESTS_SAMPLE_FILES_PATH) + QStringLiteral("/music/testMany.ogg")), mimeDb); + auto scannedTrack = fileScanner.scanOneFile(QUrl::fromLocalFile(QStringLiteral(LOCAL_FILE_TESTS_SAMPLE_FILES_PATH) + QStringLiteral("/music/test.ogg"))); + auto scannedTrack2 = fileScanner.scanOneFile(QUrl::fromLocalFile(QStringLiteral(LOCAL_FILE_TESTS_SAMPLE_FILES_PATH) + QStringLiteral("/music/testMultiple.ogg"))); + auto scannedTrack3 = fileScanner.scanOneFile(QUrl::fromLocalFile(QStringLiteral(LOCAL_FILE_TESTS_SAMPLE_FILES_PATH) + QStringLiteral("/music/testMany.ogg"))); } } } void benchmarkCoverInDirectory() { FileScanner fileScanner; QBENCHMARK { fileScanner.searchForCoverFile(mTestTracksForDirectory.at(0)); fileScanner.searchForCoverFile(mTestTracksForDirectory.at(1)); fileScanner.searchForCoverFile(mTestTracksForDirectory.at(2)); fileScanner.searchForCoverFile(mTestTracksForDirectory.at(3)); fileScanner.searchForCoverFile(mTestTracksForDirectory.at(4)); fileScanner.searchForCoverFile(mTestTracksForDirectory.at(5)); fileScanner.searchForCoverFile(mTestTracksForDirectory.at(6)); fileScanner.searchForCoverFile(mTestTracksForDirectory.at(7)); fileScanner.searchForCoverFile(mTestTracksForDirectory.at(8)); } } void benchmarkCoverFromMetadata() { FileScanner fileScanner; QBENCHMARK { fileScanner.checkEmbeddedCoverImage(mTestTracksForMetaData.at(0)); fileScanner.checkEmbeddedCoverImage(mTestTracksForMetaData.at(1)); fileScanner.checkEmbeddedCoverImage(mTestTracksForMetaData.at(2)); } } }; QTEST_GUILESS_MAIN(FileScannerTest) #include "filescannertest.moc" diff --git a/src/abstractfile/abstractfilelisting.cpp b/src/abstractfile/abstractfilelisting.cpp index aa7682d0..2452f296 100644 --- a/src/abstractfile/abstractfilelisting.cpp +++ b/src/abstractfile/abstractfilelisting.cpp @@ -1,480 +1,471 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "abstractfilelisting.h" #include "config-upnp-qt.h" #include "abstractfile/indexercommon.h" #include "filescanner.h" #include #include #include #include #include #include -#include #include #include #include #include #include #include class AbstractFileListingPrivate { public: QStringList mAllRootPaths; QFileSystemWatcher mFileSystemWatcher; QHash mAllAlbumCover; QHash>> mDiscoveredFiles; FileScanner mFileScanner; - QMimeDatabase mMimeDb; - QHash mAllFiles; QAtomicInt mStopRequest = 0; int mImportedTracksCount = 0; int mNewFilesEmitInterval = 1; bool mHandleNewFiles = true; bool mWaitEndTrackRemoval = false; bool mErrorWatchingFileSystemChanges = false; }; AbstractFileListing::AbstractFileListing(QObject *parent) : QObject(parent), d(std::make_unique()) { connect(&d->mFileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, &AbstractFileListing::directoryChanged); connect(&d->mFileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &AbstractFileListing::fileChanged); } AbstractFileListing::~AbstractFileListing() = default; void AbstractFileListing::init() { Q_EMIT askRestoredTracks(); } void AbstractFileListing::newTrackFile(const DataTypes::TrackDataType &partialTrack) { auto scanFileInfo = QFileInfo(partialTrack.resourceURI().toLocalFile()); const auto &newTrack = scanOneFile(partialTrack.resourceURI(), scanFileInfo); if (newTrack.isValid() && newTrack != partialTrack) { Q_EMIT modifyTracksList({newTrack}, d->mAllAlbumCover); } } void AbstractFileListing::restoredTracks(QHash allFiles) { executeInit(std::move(allFiles)); refreshContent(); } void AbstractFileListing::setAllRootPaths(const QStringList &allRootPaths) { //resolve symlinks QStringList allPaths; for (const auto &path : allRootPaths) { QFileInfo newPath(path); if (newPath.isSymLink()) { allPaths << newPath.symLinkTarget(); } else { allPaths << path; } } d->mAllRootPaths = allPaths; } void AbstractFileListing::databaseFinishedInsertingTracksList() { } void AbstractFileListing::databaseFinishedRemovingTracksList() { if (waitEndTrackRemoval()) { Q_EMIT indexingFinished(); setWaitEndTrackRemoval(false); } } void AbstractFileListing::applicationAboutToQuit() { d->mStopRequest = 1; } const QStringList &AbstractFileListing::allRootPaths() const { return d->mAllRootPaths; } void AbstractFileListing::scanDirectory(DataTypes::ListTrackDataType &newFiles, const QUrl &path) { if (d->mStopRequest == 1) { return; } QDir rootDirectory(path.toLocalFile()); rootDirectory.refresh(); if (rootDirectory.exists()) { watchPath(path.toLocalFile()); } auto ¤tDirectoryListingFiles = d->mDiscoveredFiles[path]; auto currentFilesList = QSet(); rootDirectory.refresh(); const auto entryList = rootDirectory.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); for (const auto &oneEntry : entryList) { auto newFilePath = QUrl::fromLocalFile(oneEntry.canonicalFilePath()); if (oneEntry.isDir() || oneEntry.isFile()) { currentFilesList.insert(newFilePath); } } auto removedTracks = QVector>(); for (const auto &removedFilePath : currentDirectoryListingFiles) { auto itFilePath = std::find(currentFilesList.begin(), currentFilesList.end(), removedFilePath.first); if (itFilePath != currentFilesList.end()) { continue; } removedTracks.push_back(removedFilePath); } auto allRemovedTracks = QList(); for (const auto &oneRemovedTrack : removedTracks) { if (oneRemovedTrack.second) { allRemovedTracks.push_back(oneRemovedTrack.first); } else { removeFile(oneRemovedTrack.first, allRemovedTracks); } } for (const auto &oneRemovedTrack : removedTracks) { currentDirectoryListingFiles.remove(oneRemovedTrack); currentDirectoryListingFiles.remove(oneRemovedTrack); } if (!allRemovedTracks.isEmpty()) { Q_EMIT removedTracksList(allRemovedTracks); } if (!d->mHandleNewFiles) { return; } for (const auto &newFilePath : currentFilesList) { QFileInfo oneEntry(newFilePath.toLocalFile()); auto itFilePath = std::find(currentDirectoryListingFiles.begin(), currentDirectoryListingFiles.end(), QPair{newFilePath, oneEntry.isFile()}); if (itFilePath != currentDirectoryListingFiles.end()) { continue; } if (oneEntry.isDir()) { addFileInDirectory(newFilePath, path); scanDirectory(newFiles, newFilePath); if (d->mStopRequest == 1) { break; } continue; } if (!oneEntry.isFile()) { continue; } auto newTrack = scanOneFile(newFilePath, oneEntry); if (newTrack.isValid() && d->mStopRequest == 0) { addCover(newTrack); addFileInDirectory(newTrack.resourceURI(), path); newFiles.push_back(newTrack); ++d->mImportedTracksCount; if (newFiles.size() > d->mNewFilesEmitInterval && d->mStopRequest == 0) { d->mNewFilesEmitInterval = std::min(50, 1 + d->mNewFilesEmitInterval * d->mNewFilesEmitInterval); emitNewFiles(newFiles); newFiles.clear(); } } else { qCDebug(orgKdeElisaIndexer()) << "AbstractFileListing::scanDirectory" << newFilePath << "is not a valid track"; } if (d->mStopRequest == 1) { break; } } } void AbstractFileListing::directoryChanged(const QString &path) { const auto directoryEntry = d->mDiscoveredFiles.find(QUrl::fromLocalFile(path)); if (directoryEntry == d->mDiscoveredFiles.end()) { return; } Q_EMIT indexingStarted(); scanDirectoryTree(path); Q_EMIT indexingFinished(); } void AbstractFileListing::fileChanged(const QString &modifiedFileName) { QFileInfo modifiedFileInfo(modifiedFileName); auto modifiedFile = QUrl::fromLocalFile(modifiedFileName); auto modifiedTrack = scanOneFile(modifiedFile, modifiedFileInfo); if (modifiedTrack.isValid()) { Q_EMIT modifyTracksList({modifiedTrack}, d->mAllAlbumCover); } } void AbstractFileListing::executeInit(QHash allFiles) { d->mAllFiles = std::move(allFiles); } void AbstractFileListing::triggerRefreshOfContent() { d->mImportedTracksCount = 0; } void AbstractFileListing::refreshContent() { triggerRefreshOfContent(); } DataTypes::TrackDataType AbstractFileListing::scanOneFile(const QUrl &scanFile, const QFileInfo &scanFileInfo) { DataTypes::TrackDataType newTrack; qCDebug(orgKdeElisaIndexer) << "AbstractFileListing::scanOneFile" << scanFile; auto localFileName = scanFile.toLocalFile(); - const auto &fileMimeType = d->mMimeDb.mimeTypeForFile(localFileName); - if (!fileMimeType.name().startsWith(QLatin1String("audio/"))) { + if (!d->mFileScanner.shouldScanFile(localFileName)) { return newTrack; } if (scanFileInfo.exists()) { auto itExistingFile = d->mAllFiles.find(scanFile); if (itExistingFile != d->mAllFiles.end()) { if (*itExistingFile >= scanFileInfo.metadataChangeTime()) { d->mAllFiles.erase(itExistingFile); return newTrack; } } } - newTrack = d->mFileScanner.scanOneFile(scanFile, d->mMimeDb); + newTrack = d->mFileScanner.scanOneFile(scanFile); if (newTrack.isValid()) { newTrack[DataTypes::HasEmbeddedCover] = checkEmbeddedCoverImage(localFileName); newTrack[DataTypes::FileModificationTime] = scanFileInfo.metadataChangeTime(); if (scanFileInfo.exists()) { watchPath(scanFile.toLocalFile()); } } return newTrack; } void AbstractFileListing::watchPath(const QString &pathName) { if (!d->mFileSystemWatcher.addPath(pathName)) { qCDebug(orgKdeElisaIndexer) << "AbstractFileListing::watchPath" << "fail for" << pathName; if (!d->mErrorWatchingFileSystemChanges) { d->mErrorWatchingFileSystemChanges = true; Q_EMIT errorWatchingFileSystemChanges(); } } } void AbstractFileListing::addFileInDirectory(const QUrl &newFile, const QUrl &directoryName) { const auto directoryEntry = d->mDiscoveredFiles.find(directoryName); if (directoryEntry == d->mDiscoveredFiles.end()) { watchPath(directoryName.toLocalFile()); QDir currentDirectory(directoryName.toLocalFile()); if (currentDirectory.cdUp()) { const auto parentDirectoryName = currentDirectory.absolutePath(); const auto parentDirectory = QUrl::fromLocalFile(parentDirectoryName); const auto parentDirectoryEntry = d->mDiscoveredFiles.find(parentDirectory); if (parentDirectoryEntry == d->mDiscoveredFiles.end()) { watchPath(parentDirectoryName); } auto &parentCurrentDirectoryListingFiles = d->mDiscoveredFiles[parentDirectory]; parentCurrentDirectoryListingFiles.insert({directoryName, false}); } } auto ¤tDirectoryListingFiles = d->mDiscoveredFiles[directoryName]; QFileInfo isAFile(newFile.toLocalFile()); currentDirectoryListingFiles.insert({newFile, isAFile.isFile()}); } void AbstractFileListing::scanDirectoryTree(const QString &path) { auto newFiles = DataTypes::ListTrackDataType(); qCDebug(orgKdeElisaIndexer()) << "AbstractFileListing::scanDirectoryTree" << path; scanDirectory(newFiles, QUrl::fromLocalFile(path)); if (!newFiles.isEmpty() && d->mStopRequest == 0) { emitNewFiles(newFiles); } } void AbstractFileListing::setHandleNewFiles(bool handleThem) { d->mHandleNewFiles = handleThem; } void AbstractFileListing::emitNewFiles(const DataTypes::ListTrackDataType &tracks) { Q_EMIT tracksList(tracks, d->mAllAlbumCover); } void AbstractFileListing::addCover(const DataTypes::TrackDataType &newTrack) { auto itCover = d->mAllAlbumCover.find(newTrack.album()); if (itCover != d->mAllAlbumCover.end()) { return; } auto coverUrl = d->mFileScanner.searchForCoverFile(newTrack.resourceURI().toLocalFile()); if (!coverUrl.isEmpty()) { d->mAllAlbumCover[newTrack.resourceURI().toString()] = coverUrl; } } void AbstractFileListing::removeDirectory(const QUrl &removedDirectory, QList &allRemovedFiles) { const auto itRemovedDirectory = d->mDiscoveredFiles.find(removedDirectory); if (itRemovedDirectory == d->mDiscoveredFiles.end()) { return; } const auto ¤tRemovedDirectory = *itRemovedDirectory; for (const auto &itFile : currentRemovedDirectory) { if (itFile.first.isValid() && !itFile.first.isEmpty()) { removeFile(itFile.first, allRemovedFiles); if (itFile.second) { allRemovedFiles.push_back(itFile.first); } } } d->mDiscoveredFiles.erase(itRemovedDirectory); } void AbstractFileListing::removeFile(const QUrl &oneRemovedTrack, QList &allRemovedFiles) { auto itRemovedDirectory = d->mDiscoveredFiles.find(oneRemovedTrack); if (itRemovedDirectory != d->mDiscoveredFiles.end()) { removeDirectory(oneRemovedTrack, allRemovedFiles); } } QHash &AbstractFileListing::allFiles() { return d->mAllFiles; } void AbstractFileListing::checkFilesToRemove() { QList allRemovedFiles; for (auto itFile = d->mAllFiles.begin(); itFile != d->mAllFiles.end(); ++itFile) { allRemovedFiles.push_back(itFile.key()); } qCDebug(orgKdeElisaIndexer()) << "AbstractFileListing::checkFilesToRemove" << allRemovedFiles.size(); if (!allRemovedFiles.isEmpty()) { setWaitEndTrackRemoval(true); Q_EMIT removedTracksList(allRemovedFiles); } } FileScanner &AbstractFileListing::fileScanner() { return d->mFileScanner; } bool AbstractFileListing::checkEmbeddedCoverImage(const QString &localFileName) { return d->mFileScanner.checkEmbeddedCoverImage(localFileName); } bool AbstractFileListing::waitEndTrackRemoval() const { return d->mWaitEndTrackRemoval; } void AbstractFileListing::setWaitEndTrackRemoval(bool wait) { d->mWaitEndTrackRemoval = wait; } -const QMimeDatabase &AbstractFileListing::mimeDatabase() const -{ - return d->mMimeDb; -} - #include "moc_abstractfilelisting.cpp" diff --git a/src/abstractfile/abstractfilelisting.h b/src/abstractfile/abstractfilelisting.h index b906d2f8..1bcd5796 100644 --- a/src/abstractfile/abstractfilelisting.h +++ b/src/abstractfile/abstractfilelisting.h @@ -1,139 +1,136 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef ABSTRACTFILELISTING_H #define ABSTRACTFILELISTING_H #include "elisaLib_export.h" #include "datatypes.h" #include #include #include #include #include #include #include class AbstractFileListingPrivate; class FileScanner; class QFileInfo; -class QMimeDatabase; class ELISALIB_EXPORT AbstractFileListing : public QObject { Q_OBJECT public: explicit AbstractFileListing(QObject *parent = nullptr); ~AbstractFileListing() override; virtual void applicationAboutToQuit(); const QStringList& allRootPaths() const; Q_SIGNALS: void tracksList(const DataTypes::ListTrackDataType &tracks, const QHash &covers); void removedTracksList(const QList &removedTracks); void modifyTracksList(const DataTypes::ListTrackDataType &modifiedTracks, const QHash &covers); void indexingStarted(); void indexingFinished(); void askRestoredTracks(); void errorWatchingFileSystemChanges(); public Q_SLOTS: void refreshContent(); void init(); void newTrackFile(const DataTypes::TrackDataType &partialTrack); void restoredTracks(QHash allFiles); void setAllRootPaths(const QStringList &allRootPaths); void databaseFinishedInsertingTracksList(); void databaseFinishedRemovingTracksList(); protected Q_SLOTS: void directoryChanged(const QString &path); void fileChanged(const QString &modifiedFileName); protected: virtual void executeInit(QHash allFiles); virtual void triggerRefreshOfContent(); void scanDirectory(DataTypes::ListTrackDataType &newFiles, const QUrl &path); virtual DataTypes::TrackDataType scanOneFile(const QUrl &scanFile, const QFileInfo &scanFileInfo); 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 DataTypes::ListTrackDataType &tracks); void addCover(const DataTypes::TrackDataType &newTrack); void removeDirectory(const QUrl &removedDirectory, QList &allRemovedFiles); void removeFile(const QUrl &oneRemovedTrack, QList &allRemovedFiles); QHash& allFiles(); void checkFilesToRemove(); FileScanner& fileScanner(); bool checkEmbeddedCoverImage(const QString &localFileName); bool waitEndTrackRemoval() const; void setWaitEndTrackRemoval(bool wait); - const QMimeDatabase& mimeDatabase() const; - private: std::unique_ptr d; }; #endif // ABSTRACTFILELISTING_H diff --git a/src/baloo/localbaloofilelisting.cpp b/src/baloo/localbaloofilelisting.cpp index ea34d190..02eea47c 100644 --- a/src/baloo/localbaloofilelisting.cpp +++ b/src/baloo/localbaloofilelisting.cpp @@ -1,421 +1,420 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "localbaloofilelisting.h" #include "baloo/baloocommon.h" #include "elisa_settings.h" #include "elisautils.h" #include "baloo/scheduler.h" #include "baloo/fileindexer.h" #include "baloo/main.h" #include "baloowatcherapplicationadaptor.h" #include "filescanner.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class LocalBalooFileListingPrivate { public: Baloo::Query mQuery; QDBusServiceWatcher mServiceWatcher; QScopedPointer mBalooMainInterface; QScopedPointer mBalooIndexer; QScopedPointer mBalooScheduler; BalooWatcherApplicationAdaptor *mDbusAdaptor = nullptr; QAtomicInt mStopRequest = 0; bool mIsRegisteredToBaloo = false; bool mIsRegisteringToBaloo = false; bool mIsRegisteredToBalooWatcher = false; bool mIsRegisteringToBalooWatcher = false; }; LocalBalooFileListing::LocalBalooFileListing(QObject *parent) : AbstractFileListing(parent), d(std::make_unique()) { d->mQuery.addType(QStringLiteral("Audio")); setHandleNewFiles(false); auto sessionBus = QDBusConnection::sessionBus(); d->mDbusAdaptor = new BalooWatcherApplicationAdaptor(this); sessionBus.registerObject(QStringLiteral("/org/kde/BalooWatcherApplication"), d->mDbusAdaptor, QDBusConnection::ExportAllContents); connect(&d->mServiceWatcher, &QDBusServiceWatcher::serviceRegistered, this, &LocalBalooFileListing::serviceRegistered); connect(&d->mServiceWatcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &LocalBalooFileListing::serviceOwnerChanged); connect(&d->mServiceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &LocalBalooFileListing::serviceUnregistered); d->mServiceWatcher.setConnection(sessionBus); d->mServiceWatcher.addWatchedService(QStringLiteral("org.kde.baloo")); if (sessionBus.interface()->isServiceRegistered(QStringLiteral("org.kde.baloo"))) { registerToBaloo(); } } LocalBalooFileListing::~LocalBalooFileListing() { } void LocalBalooFileListing::applicationAboutToQuit() { AbstractFileListing::applicationAboutToQuit(); d->mStopRequest = 1; } void LocalBalooFileListing::newBalooFile(const QString &fileName) { qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::newBalooFile" << fileName; auto scanFileInfo = QFileInfo(fileName); if (!scanFileInfo.exists()) { return; } - const auto &fileMimeType = mimeDatabase().mimeTypeForFile(fileName); - if (!fileMimeType.name().startsWith(QLatin1String("audio/"))) { + if (!fileScanner().shouldScanFile(fileName)) { return; } Q_EMIT indexingStarted(); auto newFile = QUrl::fromLocalFile(fileName); auto newTrack = scanOneFile(newFile, scanFileInfo); if (newTrack.isValid()) { QFileInfo newFileInfo(fileName); addFileInDirectory(newFile, QUrl::fromLocalFile(newFileInfo.absoluteDir().absolutePath())); emitNewFiles({newTrack}); } Q_EMIT indexingFinished(); } void LocalBalooFileListing::registeredToBaloo(QDBusPendingCallWatcher *watcher) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registeredToBaloo"; if (!watcher) { return; } QDBusPendingReply<> reply = *watcher; if (reply.isError()) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registeredToBaloo" << reply.error().name() << reply.error().message(); d->mIsRegisteredToBaloo = false; } else { d->mIsRegisteredToBaloo = true; } d->mIsRegisteringToBaloo = false; watcher->deleteLater(); } void LocalBalooFileListing::registeredToBalooWatcher(QDBusPendingCallWatcher *watcher) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registeredToBalooWatcher"; if (!watcher) { return; } QDBusPendingReply<> reply = *watcher; if (reply.isError()) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registeredToBalooWatcher" << reply.error().name() << reply.error().message(); d->mIsRegisteredToBalooWatcher = false; } else { d->mIsRegisteredToBalooWatcher = true; } d->mIsRegisteringToBalooWatcher = false; watcher->deleteLater(); } void LocalBalooFileListing::registerToBaloo() { if (d->mIsRegisteringToBaloo || d->mIsRegisteringToBalooWatcher) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registerToBaloo" << "already registering"; return; } qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registerToBaloo"; d->mIsRegisteringToBaloo = true; d->mIsRegisteringToBalooWatcher = true; auto sessionBus = QDBusConnection::sessionBus(); d->mBalooMainInterface.reset(new org::kde::baloo::main(QStringLiteral("org.kde.baloo"), QStringLiteral("/"), sessionBus, this)); if (!d->mBalooMainInterface->isValid()) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registerToBaloo" << "invalid org.kde.baloo/main interface"; return; } d->mBalooIndexer.reset(new org::kde::baloo::fileindexer(QStringLiteral("org.kde.baloo"), QStringLiteral("/fileindexer"), sessionBus, this)); if (!d->mBalooIndexer->isValid()) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registerToBaloo" << "invalid org.kde.baloo/fileindexer interface"; return; } connect(d->mBalooIndexer.data(), &org::kde::baloo::fileindexer::finishedIndexingFile, this, &LocalBalooFileListing::newBalooFile); d->mBalooScheduler.reset(new org::kde::baloo::scheduler(QStringLiteral("org.kde.baloo"), QStringLiteral("/scheduler"), sessionBus, this)); if (!d->mBalooScheduler->isValid()) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registerToBaloo" << "invalid org.kde.baloo/scheduler interface"; return; } qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registerToBaloo" << "call registerMonitor"; auto answer = d->mBalooIndexer->registerMonitor(); if (answer.isError()) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::executeInit" << answer.error().name() << answer.error().message(); } auto pendingCallWatcher = new QDBusPendingCallWatcher(answer); connect(pendingCallWatcher, &QDBusPendingCallWatcher::finished, this, &LocalBalooFileListing::registeredToBaloo); if (pendingCallWatcher->isFinished()) { registeredToBaloo(pendingCallWatcher); } auto pendingCall = d->mBalooMainInterface->registerBalooWatcher(QStringLiteral("org.mpris.MediaPlayer2.elisa/org/kde/BalooWatcherApplication")); qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::registerToBaloo" << "call registerBalooWatcher"; auto pendingCallWatcher2 = new QDBusPendingCallWatcher(pendingCall); connect(pendingCallWatcher2, &QDBusPendingCallWatcher::finished, this, &LocalBalooFileListing::registeredToBalooWatcher); if (pendingCallWatcher2->isFinished()) { registeredToBalooWatcher(pendingCallWatcher2); } } void LocalBalooFileListing::renamedFiles(const QString &from, const QString &to, const QStringList &listFiles) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::renamedFiles" << from << to << listFiles; } void LocalBalooFileListing::serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner) { Q_UNUSED(oldOwner); Q_UNUSED(newOwner); qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::serviceOwnerChanged" << serviceName << oldOwner << newOwner; if (serviceName == QLatin1String("org.kde.baloo") && !newOwner.isEmpty()) { d->mIsRegisteredToBaloo = false; d->mIsRegisteredToBalooWatcher = false; registerToBaloo(); } } void LocalBalooFileListing::serviceRegistered(const QString &serviceName) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::serviceRegistered" << serviceName; if (serviceName == QLatin1String("org.kde.baloo")) { registerToBaloo(); } } void LocalBalooFileListing::serviceUnregistered(const QString &serviceName) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::serviceUnregistered" << serviceName; if (serviceName == QLatin1String("org.kde.baloo")) { d->mIsRegisteredToBaloo = false; d->mIsRegisteredToBalooWatcher = false; } } void LocalBalooFileListing::executeInit(QHash allFiles) { AbstractFileListing::executeInit(std::move(allFiles)); } void LocalBalooFileListing::triggerRefreshOfContent() { qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent"; Q_EMIT indexingStarted(); AbstractFileListing::triggerRefreshOfContent(); const auto &rootPaths = allRootPaths(); bool hasSingleRootPath = (rootPaths.size() == 1); auto singleRootPath = rootPaths.at(0); auto resultIterator = d->mQuery.exec(); auto newFiles = DataTypes::ListTrackDataType(); while(resultIterator.next() && d->mStopRequest == 0) { const auto &fileName = resultIterator.filePath(); if (hasSingleRootPath) { if (!fileName.startsWith(singleRootPath)) { qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent" << fileName << "does not match root paths"; continue; } } else { bool isIncluded = false; for (const auto &oneRootPath : rootPaths) { if (fileName.startsWith(oneRootPath)) { isIncluded = true; break; } } if (!isIncluded) { qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent" << fileName << "does not match root paths"; continue; } } const auto &newFileUrl = QUrl::fromLocalFile(resultIterator.filePath()); auto scanFileInfo = QFileInfo(fileName); if (!scanFileInfo.exists()) { qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent" << fileName << "file does not exists"; continue; } auto itExistingFile = allFiles().find(newFileUrl); if (itExistingFile != allFiles().end()) { if (*itExistingFile >= scanFileInfo.fileTime(QFile::FileModificationTime)) { allFiles().erase(itExistingFile); qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent" << fileName << "file not modified since last scan"; continue; } } const auto currentDirectory = QUrl::fromLocalFile(scanFileInfo.absoluteDir().absolutePath()); addFileInDirectory(newFileUrl, currentDirectory); const auto &newTrack = scanOneFile(newFileUrl, scanFileInfo); if (newTrack.isValid()) { newFiles.push_back(newTrack); if (newFiles.size() > 500 && d->mStopRequest == 0) { qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent" << "insert new tracks in database" << newFiles.count(); emitNewFiles(newFiles); newFiles.clear(); } } else { qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent" << fileName << "invalid track" << newTrack; } } if (!newFiles.isEmpty() && d->mStopRequest == 0) { qCDebug(orgKdeElisaBaloo()) << "LocalBalooFileListing::triggerRefreshOfContent" << "insert new tracks in database" << newFiles.count(); emitNewFiles(newFiles); } setWaitEndTrackRemoval(false); checkFilesToRemove(); if (!waitEndTrackRemoval()) { Q_EMIT indexingFinished(); } } DataTypes::TrackDataType LocalBalooFileListing::scanOneFile(const QUrl &scanFile, const QFileInfo &scanFileInfo) { DataTypes::TrackDataType trackData; auto localFileName = scanFile.toLocalFile(); Baloo::File match(localFileName); match.load(); fileScanner().scanProperties(match, trackData); trackData[DataTypes::FileModificationTime] = scanFileInfo.metadataChangeTime(); trackData[DataTypes::ResourceRole] = scanFile; if (!trackData.isValid()) { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::scanOneFile" << scanFile << "falling back to plain file metadata analysis"; trackData = AbstractFileListing::scanOneFile(scanFile, scanFileInfo); } if (trackData.isValid()) { trackData[DataTypes::HasEmbeddedCover] = checkEmbeddedCoverImage(localFileName); addCover(trackData); } else { qCDebug(orgKdeElisaBaloo) << "LocalBalooFileListing::scanOneFile" << scanFile << "invalid track"; } return trackData; } #include "moc_localbaloofilelisting.cpp" diff --git a/src/file/localfilelisting.cpp b/src/file/localfilelisting.cpp index bf5adf72..983816e3 100644 --- a/src/file/localfilelisting.cpp +++ b/src/file/localfilelisting.cpp @@ -1,78 +1,77 @@ /* * Copyright 2016-2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "localfilelisting.h" #include "musicaudiotrack.h" #include "abstractfile/indexercommon.h" #include #include #include #include #include -#include #include #include #include class LocalFileListingPrivate { public: }; LocalFileListing::LocalFileListing(QObject *parent) : AbstractFileListing(parent), d(std::make_unique()) { } LocalFileListing::~LocalFileListing() = default; void LocalFileListing::executeInit(QHash allFiles) { qCDebug(orgKdeElisaIndexer()) << "LocalFileListing::executeInit" << "with" << allFiles.size() << "files"; AbstractFileListing::executeInit(std::move(allFiles)); } void LocalFileListing::triggerRefreshOfContent() { Q_EMIT indexingStarted(); qCDebug(orgKdeElisaIndexer()) << "LocalFileListing::triggerRefreshOfContent" << allRootPaths(); AbstractFileListing::triggerRefreshOfContent(); const auto &rootPaths = allRootPaths(); for (const auto &onePath : rootPaths) { scanDirectoryTree(onePath); } setWaitEndTrackRemoval(false); checkFilesToRemove(); if (!waitEndTrackRemoval()) { Q_EMIT indexingFinished(); } } #include "moc_localfilelisting.cpp" diff --git a/src/filescanner.cpp b/src/filescanner.cpp index 5ab0c72d..a6a79a03 100644 --- a/src/filescanner.cpp +++ b/src/filescanner.cpp @@ -1,257 +1,269 @@ /* * Copyright 2018 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "filescanner.h" #include "config-upnp-qt.h" #include "abstractfile/indexercommon.h" #if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND #include #include #include #include #include #include #if defined KF5Baloo_FOUND && KF5Baloo_FOUND #include #endif #endif #include #include #include #include #include +#include #if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND static const QHash propertyTranslation = { {KFileMetaData::Property::Artist, DataTypes::ColumnsRoles::ArtistRole}, {KFileMetaData::Property::AlbumArtist, DataTypes::ColumnsRoles::AlbumArtistRole}, {KFileMetaData::Property::Genre, DataTypes::ColumnsRoles::GenreRole}, {KFileMetaData::Property::Composer, DataTypes::ColumnsRoles::ComposerRole}, {KFileMetaData::Property::Lyricist, DataTypes::ColumnsRoles::LyricistRole}, {KFileMetaData::Property::Title, DataTypes::ColumnsRoles::TitleRole}, {KFileMetaData::Property::Album, DataTypes::ColumnsRoles::AlbumRole}, {KFileMetaData::Property::TrackNumber, DataTypes::ColumnsRoles::TrackNumberRole}, {KFileMetaData::Property::DiscNumber, DataTypes::ColumnsRoles::DiscNumberRole}, {KFileMetaData::Property::ReleaseYear, DataTypes::ColumnsRoles::YearRole}, {KFileMetaData::Property::Lyrics, DataTypes::ColumnsRoles::LyricsRole}, {KFileMetaData::Property::Comment, DataTypes::ColumnsRoles::CommentRole}, {KFileMetaData::Property::Rating, DataTypes::ColumnsRoles::RatingRole}, {KFileMetaData::Property::Channels, DataTypes::ColumnsRoles::ChannelsRole}, {KFileMetaData::Property::SampleRate, DataTypes::ColumnsRoles::SampleRateRole}, {KFileMetaData::Property::BitRate, DataTypes::ColumnsRoles::BitRateRole}, {KFileMetaData::Property::Duration, DataTypes::ColumnsRoles::DurationRole}, }; #endif class FileScannerPrivate { public: #if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND KFileMetaData::ExtractorCollection mAllExtractors; KFileMetaData::PropertyMap mAllProperties; KFileMetaData::EmbeddedImageData mImageScanner; #endif + QMimeDatabase mMimeDb; }; static const QStringList constSearchStrings = { QStringLiteral("*[Cc]over*.jpg"), QStringLiteral("*[Cc]over*.png"), QStringLiteral("*[Ff]older*.jpg"), QStringLiteral("*[Ff]older*.png"), QStringLiteral("*[Ff]ront*.jpg"), QStringLiteral("*[Ff]ront*.png") }; FileScanner::FileScanner() : d(std::make_unique()) { } +bool FileScanner::shouldScanFile(const QString &scanFile) +{ + const auto &fileMimeType = d->mMimeDb.mimeTypeForFile(scanFile); + return fileMimeType.name().startsWith(QLatin1String("audio/")); +} + FileScanner::~FileScanner() = default; -DataTypes::TrackDataType FileScanner::scanOneFile(const QUrl &scanFile, const QMimeDatabase &mimeDatabase) +DataTypes::TrackDataType FileScanner::scanOneFile(const QUrl &scanFile) { DataTypes::TrackDataType newTrack; + if (!scanFile.isLocalFile() && !scanFile.scheme().isEmpty()) { + return newTrack; + } + auto localFileName = scanFile.toLocalFile(); QFileInfo scanFileInfo(localFileName); newTrack[DataTypes::FileModificationTime] = scanFileInfo.metadataChangeTime(); newTrack[DataTypes::ResourceRole] = scanFile; newTrack[DataTypes::RatingRole] = 0; #if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND - const auto &fileMimeType = mimeDatabase.mimeTypeForFile(localFileName); + const auto &fileMimeType = d->mMimeDb.mimeTypeForFile(localFileName); if (!fileMimeType.name().startsWith(QLatin1String("audio/"))) { return newTrack; } QString mimetype = fileMimeType.name(); QList exList = d->mAllExtractors.fetchExtractors(mimetype); if (exList.isEmpty()) { return newTrack; } KFileMetaData::Extractor* ex = exList.first(); KFileMetaData::SimpleExtractionResult result(localFileName, mimetype, KFileMetaData::ExtractionResult::ExtractMetaData); ex->extract(&result); d->mAllProperties = result.properties(); scanProperties(localFileName, newTrack); qCDebug(orgKdeElisaIndexer()) << "scanOneFile" << scanFile << "using KFileMetaData" << newTrack; #else Q_UNUSED(scanFile) Q_UNUSED(mimeDatabase) qCDebug(orgKdeElisaIndexer()) << "scanOneFile" << scanFile << "no metadata provider" << newTrack; #endif return newTrack; } void FileScanner::scanProperties(const Baloo::File &match, DataTypes::TrackDataType &trackData) { #if defined KF5Baloo_FOUND && KF5Baloo_FOUND d->mAllProperties = match.properties(); scanProperties(match.path(), trackData); qCDebug(orgKdeElisaIndexer()) << "scanProperties" << match.path() << "using Baloo" << trackData; #else Q_UNUSED(match) Q_UNUSED(trackData) qCDebug(orgKdeElisaIndexer()) << "scanProperties" << "no metadata provider" << trackData; #endif } void FileScanner::scanProperties(const QString &localFileName, DataTypes::TrackDataType &trackData) { #if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND if (d->mAllProperties.isEmpty()) { return; } using entry = std::pair; auto rangeBegin = d->mAllProperties.constKeyValueBegin(); QVariant value; while (rangeBegin != d->mAllProperties.constKeyValueEnd()) { auto key = (*rangeBegin).first; auto rangeEnd = std::find_if(rangeBegin, d->mAllProperties.constKeyValueEnd(), [key](const entry& e) { return e.first != key; }); auto distance = std::distance(rangeBegin, rangeEnd); if (distance > 1) { QStringList list; list.reserve(static_cast(distance)); std::for_each(rangeBegin, rangeEnd, [&list](const entry& s) { list.append(s.second.toString()); }); value = QLocale().createSeparatedList(list); } else { value = (*rangeBegin).second; if (value.canConvert()) { value = QLocale().createSeparatedList(value.toStringList()); } } auto translatedKey = propertyTranslation.find(key); if (translatedKey.value() == DataTypes::DurationRole) { trackData.insert(translatedKey.value(), QTime::fromMSecsSinceStartOfDay(int(1000 * (*rangeBegin).second.toDouble()))); } else if (translatedKey != propertyTranslation.end()) { trackData.insert(translatedKey.value(), (*rangeBegin).second); } rangeBegin = rangeEnd; } #if !defined Q_OS_ANDROID && !defined Q_OS_WIN auto fileData = KFileMetaData::UserMetaData(localFileName); QString comment = fileData.userComment(); if (!comment.isEmpty()) { trackData[DataTypes::CommentRole] = comment; } int rating = fileData.rating(); if (rating >= 0) { trackData[DataTypes::RatingRole] = rating; } #endif #else Q_UNUSED(localFileName) Q_UNUSED(trackData) #endif } QUrl FileScanner::searchForCoverFile(const QString &localFileName) { QFileInfo trackFilePath(localFileName); QDir trackFileDir = trackFilePath.absoluteDir(); trackFileDir.setFilter(QDir::Files); trackFileDir.setNameFilters(constSearchStrings); QFileInfoList coverFiles = trackFileDir.entryInfoList(); if (coverFiles.isEmpty()) { QString dirNamePattern = QLatin1String("*") + trackFileDir.dirName() + QLatin1String("*"); QString dirNameNoSpaces = dirNamePattern.remove(QLatin1Char(' ')); QStringList filters = { dirNamePattern + QStringLiteral(".jpg"), dirNamePattern + QStringLiteral(".png"), dirNameNoSpaces + QStringLiteral(".jpg"), dirNameNoSpaces + QStringLiteral(".png") }; trackFileDir.setNameFilters(filters); coverFiles = trackFileDir.entryInfoList(); } if (coverFiles.isEmpty()) { return QUrl(); } return QUrl::fromLocalFile(coverFiles.first().absoluteFilePath()); } bool FileScanner::checkEmbeddedCoverImage(const QString &localFileName) { #if defined KF5FileMetaData_FOUND && KF5FileMetaData_FOUND auto imageData = d->mImageScanner.imageData(localFileName); if (imageData.contains(KFileMetaData::EmbeddedImageData::FrontCover)) { if (!imageData[KFileMetaData::EmbeddedImageData::FrontCover].isEmpty()) { return true; } } #else Q_UNUSED(localFileName) #endif return false; } diff --git a/src/filescanner.h b/src/filescanner.h index 9e5170db..6479af29 100644 --- a/src/filescanner.h +++ b/src/filescanner.h @@ -1,62 +1,63 @@ /* * Copyright 2018 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef FILESCANNER_H #define FILESCANNER_H #include "elisaLib_export.h" #include "datatypes.h" #include -#include #include namespace Baloo { class File; } class FileScannerPrivate; class ELISALIB_EXPORT FileScanner { public: FileScanner(); virtual ~FileScanner(); - DataTypes::TrackDataType scanOneFile(const QUrl &scanFile, const QMimeDatabase &mimeDatabase); + bool shouldScanFile(const QString &scanFile); + + DataTypes::TrackDataType scanOneFile(const QUrl &scanFile); void scanProperties(const Baloo::File &match, DataTypes::TrackDataType &trackData); void scanProperties(const QString &localFileName, DataTypes::TrackDataType &trackData); QUrl searchForCoverFile(const QString &localFileName); bool checkEmbeddedCoverImage(const QString &localFileName); private: std::unique_ptr d; }; #endif // FILESCANNER_H diff --git a/src/models/filebrowsermodel.h b/src/models/filebrowsermodel.h index cc7788c5..a22af86e 100644 --- a/src/models/filebrowsermodel.h +++ b/src/models/filebrowsermodel.h @@ -1,62 +1,61 @@ /* * Copyright 2018 Alexander Stippich * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef FILEBROWSERMODEL_H #define FILEBROWSERMODEL_H #include "elisaLib_export.h" #include -#include class MusicAudioTrack; class ELISALIB_EXPORT FileBrowserModel : public KDirModel { Q_OBJECT public: enum ColumnsRoles { NameRole = Qt::UserRole + 1, FileUrlRole = Qt::UserRole + 2, ImageUrlRole = Qt::UserRole + 3, IsDirectoryRole = Qt::UserRole + 4, IsPlayListRole = Qt::UserRole + 5 }; Q_ENUM(ColumnsRoles) explicit FileBrowserModel(QObject *parent = nullptr); ~FileBrowserModel() override; QString url() const; QHash roleNames() const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; void setUrl(const QString &url); Q_SIGNALS: void urlChanged(); }; #endif //FILEBROWSERMODEL_H diff --git a/src/models/filebrowserproxymodel.h b/src/models/filebrowserproxymodel.h index 0faac326..136c26cd 100644 --- a/src/models/filebrowserproxymodel.h +++ b/src/models/filebrowserproxymodel.h @@ -1,125 +1,123 @@ /* * Copyright 2016-2018 Matthieu Gallien * Copyright 2018 Alexander Stippich * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef FILEBROWSERPROXYMODEL_H #define FILEBROWSERPROXYMODEL_H #include "elisaLib_export.h" #include "musicaudiotrack.h" #include "filescanner.h" #include "elisautils.h" #include #include #include #include #include class ELISALIB_EXPORT FileBrowserProxyModel : public KDirSortFilterProxyModel { Q_OBJECT Q_PROPERTY(QString filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged) Q_PROPERTY(bool canGoBack READ canGoBack NOTIFY canGoBackChanged) Q_PROPERTY(QString url READ url NOTIFY urlChanged) Q_PROPERTY(bool sortedAscending READ sortedAscending NOTIFY sortedAscendingChanged) public: explicit FileBrowserProxyModel(QObject *parent = nullptr); ~FileBrowserProxyModel() override; QString filterText() const; QString url() const; bool canGoBack() const; bool sortedAscending() const; void setSourceModel(QAbstractItemModel *sourceModel) override; public Q_SLOTS: void enqueueToPlayList(); void replaceAndPlayOfPlayList(); void setFilterText(const QString &filterText); void openParentFolder(); void openFolder(const QString &folder, bool isDisplayRoot = false); void sortModel(Qt::SortOrder order); Q_SIGNALS: void filesToEnqueue(const ElisaUtils::EntryDataList &newFiles, ElisaUtils::PlayListEntryType databaseIdType, ElisaUtils::PlayListEnqueueMode enqueueMode, ElisaUtils::PlayListEnqueueTriggerPlay triggerPlay); void urlChanged(); void canGoBackChanged(); void filterTextChanged(const QString &filterText); void sortedAscendingChanged(); protected: bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; private: QString parentFolder() const; QString mTopFolder; FileScanner mFileScanner; - QMimeDatabase mMimeDb; - QString mFilterText; QRegularExpression mFilterExpression; QReadWriteLock mDataLock; QThreadPool mThreadPool; }; #endif // FILEBROWSERPROXYMODEL_H diff --git a/src/models/trackmetadatamodel.cpp b/src/models/trackmetadatamodel.cpp index c9e4bee9..5e6d642a 100644 --- a/src/models/trackmetadatamodel.cpp +++ b/src/models/trackmetadatamodel.cpp @@ -1,561 +1,561 @@ /* * Copyright 2018 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "trackmetadatamodel.h" #include "musiclistenersmanager.h" #include #include TrackMetadataModel::TrackMetadataModel(QObject *parent) : QAbstractListModel(parent) { connect(&mLyricsValueWatcher, &QFutureWatcher::finished, this, &TrackMetadataModel::lyricsValueIsReady); } TrackMetadataModel::~TrackMetadataModel() { if (mLyricsValueWatcher.isRunning() && !mLyricsValueWatcher.isFinished()) { mLyricsValueWatcher.waitForFinished(); } } int TrackMetadataModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return mTrackData.count(); } QVariant TrackMetadataModel::data(const QModelIndex &index, int role) const { auto result = QVariant{}; const auto currentKey = mTrackKeys[index.row()]; switch (role) { case Qt::DisplayRole: switch (currentKey) { case DataTypes::TrackNumberRole: { auto trackNumber = mTrackData.trackNumber(); if (trackNumber > 0) { result = trackNumber; } break; } case DataTypes::DiscNumberRole: { auto discNumber = mTrackData.discNumber(); if (discNumber > 0) { result = discNumber; } break; } case DataTypes::ChannelsRole: { auto channels = mTrackData.channels(); if (channels > 0) { result = channels; } break; } case DataTypes::BitRateRole: { auto bitRate = mTrackData.bitRate(); if (bitRate > 0) { result = bitRate; } break; } case DataTypes::SampleRateRole: { auto sampleRate = mTrackData.sampleRate(); if (sampleRate > 0) { result = sampleRate; } break; } default: result = mTrackData[currentKey]; break; } break; case ItemNameRole: switch (currentKey) { case DataTypes::TitleRole: result = i18nc("Track title for track metadata view", "Title"); break; case DataTypes::DurationRole: result = i18nc("Duration label for track metadata view", "Duration"); break; case DataTypes::ArtistRole: result = i18nc("Track artist for track metadata view", "Artist"); break; case DataTypes::AlbumRole: result = i18nc("Album name for track metadata view", "Album"); break; case DataTypes::AlbumArtistRole: result = i18nc("Album artist for track metadata view", "Album Artist"); break; case DataTypes::TrackNumberRole: result = i18nc("Track number for track metadata view", "Track Number"); break; case DataTypes::DiscNumberRole: result = i18nc("Disc number for track metadata view", "Disc Number"); break; case DataTypes::RatingRole: result = i18nc("Rating label for information panel", "Rating"); break; case DataTypes::GenreRole: result = i18nc("Genre label for track metadata view", "Genre"); break; case DataTypes::LyricistRole: result = i18nc("Lyricist label for track metadata view", "Lyricist"); break; case DataTypes::ComposerRole: result = i18nc("Composer name for track metadata view", "Composer"); break; case DataTypes::CommentRole: result = i18nc("Comment label for track metadata view", "Comment"); break; case DataTypes::YearRole: result = i18nc("Year label for track metadata view", "Year"); break; case DataTypes::ChannelsRole: result = i18nc("Channels label for track metadata view", "Channels"); break; case DataTypes::BitRateRole: result = i18nc("Bit rate label for track metadata view", "Bit Rate"); break; case DataTypes::SampleRateRole: result = i18nc("Sample Rate label for track metadata view", "Sample Rate"); break; case DataTypes::LastPlayDate: result = i18nc("Last play date label for track metadata view", "Last played"); break; case DataTypes::PlayCounter: result = i18nc("Play counter label for track metadata view", "Play count"); break; case DataTypes::LyricsRole: result = i18nc("Lyrics label for track metadata view", "Lyrics"); break; case DataTypes::ResourceRole: result = i18nc("Radio HTTP address for radio metadata view", "Stream Http Address"); break; case DataTypes::SecondaryTextRole: case DataTypes::ImageUrlRole: case DataTypes::ShadowForImageRole: case DataTypes::ChildModelRole: case DataTypes::StringDurationRole: case DataTypes::IsValidAlbumArtistRole: case DataTypes::AllArtistsRole: case DataTypes::HighestTrackRating: case DataTypes::IdRole: case DataTypes::DatabaseIdRole: case DataTypes::IsSingleDiscAlbumRole: case DataTypes::ContainerDataRole: case DataTypes::IsPartialDataRole: case DataTypes::AlbumIdRole: case DataTypes::HasEmbeddedCover: case DataTypes::FileModificationTime: case DataTypes::FirstPlayDate: case DataTypes::PlayFrequency: case DataTypes::ElementTypeRole: break; } break; case ItemTypeRole: switch (currentKey) { case DataTypes::TitleRole: result = TextEntry; break; case DataTypes::ResourceRole: result = TextEntry; break; case DataTypes::ArtistRole: result = TextEntry; break; case DataTypes::AlbumRole: result = TextEntry; break; case DataTypes::AlbumArtistRole: result = TextEntry; break; case DataTypes::TrackNumberRole: result = IntegerEntry; break; case DataTypes::DiscNumberRole: result = IntegerEntry; break; case DataTypes::RatingRole: result = RatingEntry; break; case DataTypes::GenreRole: result = TextEntry; break; case DataTypes::LyricistRole: result = TextEntry; break; case DataTypes::ComposerRole: result = TextEntry; break; case DataTypes::CommentRole: result = TextEntry; break; case DataTypes::YearRole: result = IntegerEntry; break; case DataTypes::LastPlayDate: result = DateEntry; break; case DataTypes::PlayCounter: result = IntegerEntry; break; case DataTypes::LyricsRole: result = LongTextEntry; break; case DataTypes::DurationRole: case DataTypes::SampleRateRole: case DataTypes::BitRateRole: case DataTypes::ChannelsRole: case DataTypes::SecondaryTextRole: case DataTypes::ImageUrlRole: case DataTypes::ShadowForImageRole: case DataTypes::ChildModelRole: case DataTypes::StringDurationRole: case DataTypes::IsValidAlbumArtistRole: case DataTypes::AllArtistsRole: case DataTypes::HighestTrackRating: case DataTypes::IdRole: case DataTypes::DatabaseIdRole: case DataTypes::IsSingleDiscAlbumRole: case DataTypes::ContainerDataRole: case DataTypes::IsPartialDataRole: case DataTypes::AlbumIdRole: case DataTypes::HasEmbeddedCover: case DataTypes::FileModificationTime: case DataTypes::FirstPlayDate: case DataTypes::PlayFrequency: case DataTypes::ElementTypeRole: break; } break; } return result; } bool TrackMetadataModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (data(index, role) != value) { mTrackData[mTrackKeys[index.row()]] = value; emit dataChanged(index, index, QVector() << role); return true; } return false; } QHash TrackMetadataModel::roleNames() const { auto names = QAbstractListModel::roleNames(); names[ItemNameRole] = "name"; names[ItemTypeRole] = "type"; return names; } QString TrackMetadataModel::fileUrl() const { return mFileUrl; } QUrl TrackMetadataModel::coverUrl() const { if (mCoverImage.isEmpty()) { return QUrl(QStringLiteral("image://icon/media-optical-audio")); } else { return mCoverImage; } } MusicListenersManager *TrackMetadataModel::manager() const { return mManager; } QString TrackMetadataModel::lyrics() const { return mFullData[TrackDataType::key_type::LyricsRole].toString(); } qulonglong TrackMetadataModel::databaseId() const { return mDatabaseId; } void TrackMetadataModel::trackData(const TrackMetadataModel::TrackDataType &trackData) { if (!mFullData.isEmpty() && trackData.databaseId() != mFullData.databaseId()) { return; } const QList fieldsForTrack({DataTypes::TitleRole, DataTypes::ArtistRole, DataTypes::AlbumRole, DataTypes::AlbumArtistRole, DataTypes::TrackNumberRole, DataTypes::DiscNumberRole, DataTypes::RatingRole, DataTypes::GenreRole, DataTypes::LyricistRole, DataTypes::ComposerRole, DataTypes::CommentRole, DataTypes::YearRole, DataTypes::LastPlayDate, DataTypes::PlayCounter}); fillDataFromTrackData(trackData, fieldsForTrack); } void TrackMetadataModel::fillDataFromTrackData(const TrackMetadataModel::TrackDataType &trackData, const QList &fieldsForTrack) { beginResetModel(); mFullData = trackData; mTrackData.clear(); mTrackKeys.clear(); for (DataTypes::ColumnsRoles role : fieldsForTrack) { if (trackData.constFind(role) != trackData.constEnd()) { if (role == DataTypes::RatingRole) { if (trackData[role].toInt() == 0) { continue; } } mTrackKeys.push_back(role); mTrackData[role] = trackData[role]; } } filterDataFromTrackData(); endResetModel(); fetchLyrics(); mDatabaseId = trackData[DataTypes::DatabaseIdRole].toULongLong(); Q_EMIT databaseIdChanged(); mCoverImage = trackData[DataTypes::ImageUrlRole].toUrl(); Q_EMIT coverUrlChanged(); auto rawFileUrl = trackData[DataTypes::ResourceRole].toUrl(); if (rawFileUrl.isLocalFile()) { mFileUrl = rawFileUrl.toLocalFile(); } else { mFileUrl = rawFileUrl.toString(); } Q_EMIT fileUrlChanged(); } void TrackMetadataModel::filterDataFromTrackData() { } void TrackMetadataModel::removeMetaData(DataTypes::ColumnsRoles metaData) { auto itMetaData = std::find(mTrackKeys.begin(), mTrackKeys.end(), metaData); if (itMetaData == mTrackKeys.end()) { return; } mTrackKeys.erase(itMetaData); mTrackData.remove(metaData); } TrackMetadataModel::TrackDataType::mapped_type TrackMetadataModel::dataFromType(TrackDataType::key_type metaData) const { return mFullData[metaData]; } void TrackMetadataModel::fillLyricsDataFromTrack() { beginInsertRows({}, mTrackData.size(), mTrackData.size()); mTrackKeys.push_back(DataTypes::LyricsRole); mTrackData[DataTypes::LyricsRole] = mLyricsValueWatcher.result(); endInsertRows(); } void TrackMetadataModel::lyricsValueIsReady() { if (!mLyricsValueWatcher.result().isEmpty()) { fillLyricsDataFromTrack(); mFullData[DataTypes::LyricsRole] = mLyricsValueWatcher.result(); Q_EMIT lyricsChanged(); } } void TrackMetadataModel::initializeById(ElisaUtils::PlayListEntryType type, qulonglong databaseId) { mFullData.clear(); mTrackData.clear(); mCoverImage.clear(); mFileUrl.clear(); Q_EMIT lyricsChanged(); Q_EMIT needDataByDatabaseId(type, databaseId); } void TrackMetadataModel::initialize(MusicListenersManager *newManager, DatabaseInterface *trackDatabase) { mManager = newManager; Q_EMIT managerChanged(); if (mManager) { mDataLoader.setDatabase(mManager->viewDatabase()); connect(this, &TrackMetadataModel::needDataByFileName, mManager->tracksListener(), &TracksListener::trackByFileNameInList); connect(mManager->tracksListener(), &TracksListener::trackHasChanged, this, &TrackMetadataModel::trackData); } else if (trackDatabase) { mDataLoader.setDatabase(trackDatabase); } if (mManager) { mManager->connectModel(&mDataLoader); } connect(this, &TrackMetadataModel::needDataByDatabaseId, &mDataLoader, &ModelDataLoader::loadDataByDatabaseId); connect(this, &TrackMetadataModel::saveRadioData, &mDataLoader, &ModelDataLoader::saveRadioModified); connect(this, &TrackMetadataModel::deleteRadioData, &mDataLoader, &ModelDataLoader::removeRadio); connect(&mDataLoader, &ModelDataLoader::trackModified, this, &TrackMetadataModel::trackData); connect(&mDataLoader, &ModelDataLoader::allTrackData, this, &TrackMetadataModel::trackData); connect(&mDataLoader, &ModelDataLoader::allRadioData, this, &TrackMetadataModel::radioData); connect(&mDataLoader, &ModelDataLoader::radioAdded, this, &TrackMetadataModel::radioData); connect(&mDataLoader, &ModelDataLoader::radioModified, this, &TrackMetadataModel::radioData); } void TrackMetadataModel::fetchLyrics() { auto lyricicsValue = QtConcurrent::run(QThreadPool::globalInstance(), [=]() { - auto trackData = mFileScanner.scanOneFile(mFullData[DataTypes::ResourceRole].toUrl(), mMimeDatabase); + auto trackData = mFileScanner.scanOneFile(mFullData[DataTypes::ResourceRole].toUrl()); if (!trackData.lyrics().isEmpty()) { return trackData.lyrics(); } return QString{}; }); mLyricsValueWatcher.setFuture(lyricicsValue); } void TrackMetadataModel::initializeForNewRadio() { mFullData.clear(); mTrackData.clear(); fillDataForNewRadio(); } void TrackMetadataModel::fillDataForNewRadio() { beginResetModel(); mTrackData.clear(); mTrackKeys.clear(); for (auto role : { DataTypes::TitleRole, DataTypes::ResourceRole, DataTypes::CommentRole, DataTypes::DatabaseIdRole }) { mTrackKeys.push_back(role); if (role == DataTypes::DatabaseIdRole) { mTrackData[role] = -1; } else { mTrackData[role] = QString(); } } filterDataFromTrackData(); endResetModel(); } void TrackMetadataModel::initializeByTrackFileName(const QString &fileName) { mFullData.clear(); mTrackData.clear(); mCoverImage.clear(); mFileUrl.clear(); Q_EMIT lyricsChanged(); Q_EMIT needDataByFileName(QUrl::fromLocalFile(fileName)); } void TrackMetadataModel::setManager(MusicListenersManager *newManager) { initialize(newManager, nullptr); } void TrackMetadataModel::setDatabase(DatabaseInterface *trackDatabase) { initialize(nullptr, trackDatabase); } void TrackMetadataModel::saveData() { Q_EMIT saveRadioData(mTrackData); } void TrackMetadataModel::deleteRadio() { if (mTrackData[DataTypes::DatabaseIdRole]>=0) { Q_EMIT deleteRadioData(mTrackData[DataTypes::DatabaseIdRole].toULongLong()); } } void TrackMetadataModel::radioData(const TrackDataType &radiosData) { if (!mFullData.isEmpty() && mFullData[DataTypes::DatabaseIdRole].toInt() != -1 && mFullData.databaseId() != radiosData.databaseId()) { return; } const QList fieldsForTrack({DataTypes::TitleRole, DataTypes::ResourceRole, DataTypes::CommentRole, DataTypes::DatabaseIdRole}); fillDataFromTrackData(radiosData, fieldsForTrack); } #include "moc_trackmetadatamodel.cpp" diff --git a/src/models/trackmetadatamodel.h b/src/models/trackmetadatamodel.h index f37845d4..4eb308e0 100644 --- a/src/models/trackmetadatamodel.h +++ b/src/models/trackmetadatamodel.h @@ -1,194 +1,191 @@ /* * Copyright 2018 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef TRACKMETADATAMODEL_H #define TRACKMETADATAMODEL_H #include "elisaLib_export.h" #include "elisautils.h" #include "trackslistener.h" #include "datatypes.h" #include "modeldataloader.h" #include "filescanner.h" #include #include -#include #include class MusicListenersManager; class ELISALIB_EXPORT TrackMetadataModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(QUrl coverUrl READ coverUrl NOTIFY coverUrlChanged) Q_PROPERTY(QString fileUrl READ fileUrl NOTIFY fileUrlChanged) Q_PROPERTY(qulonglong databaseId READ databaseId NOTIFY databaseIdChanged) Q_PROPERTY(MusicListenersManager* manager READ manager WRITE setManager NOTIFY managerChanged) Q_PROPERTY(QString lyrics READ lyrics NOTIFY lyricsChanged) public: enum ColumnRoles { ItemNameRole = Qt::UserRole + 1, ItemTypeRole, }; enum ItemType { TextEntry, IntegerEntry, RatingEntry, DateEntry, LongTextEntry, }; Q_ENUM(ItemType) using TrackDataType = DataTypes::TrackDataType; explicit TrackMetadataModel(QObject *parent = nullptr); ~TrackMetadataModel() override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QHash roleNames() const override; QUrl coverUrl() const; QString fileUrl() const; MusicListenersManager* manager() const; QString lyrics() const; qulonglong databaseId() const; Q_SIGNALS: void needDataByDatabaseId(ElisaUtils::PlayListEntryType dataType, qulonglong databaseId); void needDataByFileName(const QUrl &fileName); void coverUrlChanged(); void fileUrlChanged(); void managerChanged(); void lyricsChanged(); void saveRadioData(const DataTypes::TrackDataType &trackDataType); void deleteRadioData(qulonglong radioId); void databaseIdChanged(); public Q_SLOTS: void trackData(const TrackMetadataModel::TrackDataType &trackData); void initializeById(ElisaUtils::PlayListEntryType type, qulonglong databaseId); void initializeByTrackFileName(const QString &fileName); void initializeForNewRadio(); void setManager(MusicListenersManager *newManager); void setDatabase(DatabaseInterface *trackDatabase); void saveData(); void deleteRadio(); void radioData(const TrackMetadataModel::TrackDataType &radiosData); protected: void fillDataFromTrackData(const TrackMetadataModel::TrackDataType &trackData, const QList &fieldsForTrack); void fillDataForNewRadio(); virtual void filterDataFromTrackData(); void removeMetaData(DataTypes::ColumnsRoles metaData); TrackDataType::mapped_type dataFromType(TrackDataType::key_type metaData) const; virtual void fillLyricsDataFromTrack(); private Q_SLOTS: void lyricsValueIsReady(); private: void initialize(MusicListenersManager *newManager, DatabaseInterface *trackDatabase); void fetchLyrics(); TrackDataType mFullData; TrackDataType mTrackData; QUrl mCoverImage; QString mFileUrl; qulonglong mDatabaseId = 0; QList mTrackKeys; ModelDataLoader mDataLoader; MusicListenersManager *mManager = nullptr; FileScanner mFileScanner; - QMimeDatabase mMimeDatabase; - QFutureWatcher mLyricsValueWatcher; }; #endif // TRACKMETADATAMODEL_H diff --git a/src/trackslistener.cpp b/src/trackslistener.cpp index 99afb551..04fb15dc 100644 --- a/src/trackslistener.cpp +++ b/src/trackslistener.cpp @@ -1,250 +1,247 @@ /* * Copyright 2017 Matthieu Gallien * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "trackslistener.h" #include "playListLogging.h" #include "databaseinterface.h" #include "datatypes.h" #include "filescanner.h" -#include #include #include #include #include #include class TracksListenerPrivate { public: QSet mTracksByIdSet; QSet mRadiosByIdSet; QList> mTracksByNameSet; QList mTracksByFileNameSet; DatabaseInterface *mDatabase = nullptr; FileScanner mFileScanner; - QMimeDatabase mMimeDb; - }; TracksListener::TracksListener(DatabaseInterface *database, QObject *parent) : QObject(parent), d(std::make_unique()) { d->mDatabase = database; } TracksListener::~TracksListener() = default; void TracksListener::tracksAdded(const ListTrackDataType &allTracks) { for (const auto &oneTrack : allTracks) { if (d->mTracksByIdSet.contains(oneTrack.databaseId())) { Q_EMIT trackHasChanged(oneTrack); } if (d->mTracksByNameSet.isEmpty()) { return; } for (auto itTrack = d->mTracksByNameSet.begin(); itTrack != d->mTracksByNameSet.end(); ) { if (!std::get<0>(*itTrack).isEmpty() && std::get<0>(*itTrack) != oneTrack.title()) { ++itTrack; continue; } if (!std::get<1>(*itTrack).isEmpty() && std::get<1>(*itTrack) != oneTrack.artist()) { ++itTrack; continue; } if (!std::get<2>(*itTrack).isEmpty() && std::get<2>(*itTrack) != oneTrack.album()) { ++itTrack; continue; } if (std::get<3>(*itTrack) != oneTrack.trackNumber()) { ++itTrack; continue; } if (std::get<4>(*itTrack) != oneTrack.discNumber()) { ++itTrack; continue; } Q_EMIT trackHasChanged(TrackDataType(oneTrack)); d->mTracksByIdSet.insert(oneTrack.databaseId()); itTrack = d->mTracksByNameSet.erase(itTrack); } } } void TracksListener::trackRemoved(qulonglong id) { if (d->mTracksByIdSet.contains(id)) { Q_EMIT trackHasBeenRemoved(id); } } void TracksListener::trackModified(const TrackDataType &modifiedTrack) { if (d->mTracksByIdSet.contains(modifiedTrack.databaseId())) { Q_EMIT trackHasChanged(modifiedTrack); } } void TracksListener::trackByNameInList(const QVariant &title, const QVariant &artist, const QVariant &album, const QVariant &trackNumber, const QVariant &discNumber) { const auto realTitle = title.toString(); const auto realArtist = artist.toString(); const auto albumIsValid = !album.isNull() && album.isValid() && !album.toString().isEmpty(); auto realAlbum = std::optional{}; if (albumIsValid) { realAlbum = album.toString(); } auto trackNumberIsValid = bool{}; const auto trackNumberValue = trackNumber.toInt(&trackNumberIsValid); auto realTrackNumber = std::optional{}; if (trackNumberIsValid) { realTrackNumber = trackNumberValue; } auto discNumberIsValid = bool{}; const auto discNumberValue = discNumber.toInt(&discNumberIsValid); auto realDiscNumber = std::optional{}; if (discNumberIsValid) { realDiscNumber = discNumberValue; } auto newTrackId = d->mDatabase->trackIdFromTitleAlbumTrackDiscNumber(realTitle, realArtist, realAlbum, realTrackNumber, realDiscNumber); if (newTrackId == 0) { auto newTrack = std::tuple(realTitle, realArtist, album.toString(), trackNumber.toInt(), discNumber.toInt()); d->mTracksByNameSet.push_back(newTrack); return; } d->mTracksByIdSet.insert(newTrackId); auto newTrack = d->mDatabase->trackDataFromDatabaseId(newTrackId); if (!newTrack.isEmpty()) { Q_EMIT trackHasChanged(newTrack); } } void TracksListener::trackByFileNameInList(const QUrl &fileName) { auto newTrackId = d->mDatabase->trackIdFromFileName(fileName); if (newTrackId == 0) { - auto newTrack = d->mFileScanner.scanOneFile(fileName, d->mMimeDb); + auto newTrack = d->mFileScanner.scanOneFile(fileName); if (newTrack.isValid()) { Q_EMIT trackHasChanged(newTrack); return; } d->mTracksByFileNameSet.push_back(fileName); return; } d->mTracksByIdSet.insert(newTrackId); auto newTrack = d->mDatabase->trackDataFromDatabaseId(newTrackId); if (!newTrack.isEmpty()) { Q_EMIT trackHasChanged({newTrack}); } } void TracksListener::newAlbumInList(qulonglong newDatabaseId, const QString &entryTitle) { qCDebug(orgKdeElisaPlayList()) << "TracksListener::newAlbumInList" << newDatabaseId << entryTitle << d->mDatabase->albumData(newDatabaseId); Q_EMIT tracksListAdded(newDatabaseId, entryTitle, ElisaUtils::Album, d->mDatabase->albumData(newDatabaseId)); } void TracksListener::newEntryInList(qulonglong newDatabaseId, const QString &entryTitle, ElisaUtils::PlayListEntryType databaseIdType) { qCDebug(orgKdeElisaPlayList()) << "TracksListener::newEntryInList" << newDatabaseId << entryTitle << databaseIdType; switch (databaseIdType) { case ElisaUtils::Track: { d->mTracksByIdSet.insert(newDatabaseId); auto newTrack = d->mDatabase->trackDataFromDatabaseId(newDatabaseId); if (!newTrack.isEmpty()) { Q_EMIT trackHasChanged(newTrack); } break; } case ElisaUtils::Radio: { d->mRadiosByIdSet.insert(newDatabaseId); auto newRadio = d->mDatabase->radioDataFromDatabaseId(newDatabaseId); if (!newRadio.isEmpty()) { Q_EMIT trackHasChanged(newRadio); } break; } case ElisaUtils::Artist: newArtistInList(newDatabaseId, entryTitle); break; case ElisaUtils::FileName: trackByFileNameInList(QUrl::fromLocalFile(entryTitle)); break; case ElisaUtils::Album: newAlbumInList(newDatabaseId, entryTitle); break; case ElisaUtils::Lyricist: case ElisaUtils::Composer: case ElisaUtils::Genre: case ElisaUtils::Unknown: break; } } void TracksListener::newArtistInList(qulonglong newDatabaseId, const QString &artist) { auto newTracks = d->mDatabase->tracksDataFromAuthor(artist); if (newTracks.isEmpty()) { return; } for (const auto &oneTrack : newTracks) { d->mTracksByIdSet.insert(oneTrack.databaseId()); } Q_EMIT tracksListAdded(newDatabaseId, artist, ElisaUtils::Artist, newTracks); } #include "moc_trackslistener.cpp"